Compare commits
448 Commits
decred
...
cryptoguar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
631ccea626 | ||
|
|
a5c3c692a0 | ||
|
|
b2df4ea80d | ||
|
|
18a7105f20 | ||
|
|
fcdb2e7dfe | ||
|
|
3c5e8481cd | ||
|
|
97bb615176 | ||
|
|
f1c2b41714 | ||
|
|
8d317e4b67 | ||
|
|
45ed2cdb87 | ||
|
|
d64e3f4be9 | ||
|
|
57d885bc0c | ||
|
|
205c6e2b58 | ||
|
|
d95f3ccd24 | ||
|
|
d6a9425b22 | ||
|
|
e4cc5da490 | ||
|
|
e177d36bd4 | ||
|
|
05ffa5e3ac | ||
|
|
6165cbc4c3 | ||
|
|
7e6f94319d | ||
|
|
71fd3d10aa | ||
|
|
e4ed9aebdf | ||
|
|
b97a9f4a27 | ||
|
|
510eff6163 | ||
|
|
efb84f58af | ||
|
|
831ef40977 | ||
|
|
a0456cb689 | ||
|
|
c7818f5fac | ||
|
|
713577d868 | ||
|
|
37be3bcab5 | ||
|
|
4ae97790aa | ||
|
|
8928451af0 | ||
|
|
473e4fd400 | ||
|
|
ff2fc35f72 | ||
|
|
edb3b19dcf | ||
|
|
aac2f51b88 | ||
|
|
57b96cd985 | ||
|
|
4d5551cd84 | ||
|
|
7ee4720738 | ||
|
|
c76fe79848 | ||
|
|
f13c481b51 | ||
|
|
6f776971b1 | ||
|
|
c79ed493aa | ||
|
|
b6709d0cdc | ||
|
|
c945e267e7 | ||
|
|
ef65420978 | ||
|
|
6da4bf6aaf | ||
|
|
6e56b7f421 | ||
|
|
f084c6f538 | ||
|
|
443bd6917f | ||
|
|
b55d126a0a | ||
|
|
586ff3288f | ||
|
|
0398fce5a8 | ||
|
|
ef082ff7be | ||
|
|
168284ce25 | ||
|
|
e797e23625 | ||
|
|
d3fcdc8052 | ||
|
|
2c176a8c86 | ||
|
|
e92d5560af | ||
|
|
0171ad6889 | ||
|
|
5d381d4b73 | ||
|
|
9e24d9a12a | ||
|
|
21ef6f3129 | ||
|
|
67d808cbe4 | ||
|
|
5d1bed6423 | ||
|
|
edc11b4c96 | ||
|
|
5daf591985 | ||
|
|
aee66712b8 | ||
|
|
8de365f9d3 | ||
|
|
765ef9571a | ||
|
|
c575625097 | ||
|
|
fe02441619 | ||
|
|
c992ef571a | ||
|
|
5f275132de | ||
|
|
64151f4203 | ||
|
|
734214af53 | ||
|
|
1cb8ffb632 | ||
|
|
40d06df325 | ||
|
|
62031173f5 | ||
|
|
f473d66de5 | ||
|
|
e548cf2b3b | ||
|
|
d1baf4bc10 | ||
|
|
3b8e084b2e | ||
|
|
0a697c61e8 | ||
|
|
5af59dd8da | ||
|
|
a75cd28995 | ||
|
|
f40d98ef23 | ||
|
|
b14fba0e1f | ||
|
|
4d928dc98e | ||
|
|
1845f802a2 | ||
|
|
7ec9dfa35a | ||
|
|
b70e46ffc1 | ||
|
|
07de2d61af | ||
|
|
65fbcda556 | ||
|
|
40f334ed0e | ||
|
|
77bb3e6353 | ||
|
|
3b60472c04 | ||
|
|
b87e034719 | ||
|
|
def7aae1ec | ||
|
|
294595adbd | ||
|
|
ab04f27497 | ||
|
|
159974d414 | ||
|
|
110b91bb75 | ||
|
|
3cea5449c9 | ||
|
|
07ed0af468 | ||
|
|
feabc619ae | ||
|
|
e3f7b5b79b | ||
|
|
35bede48b0 | ||
|
|
af6154705c | ||
|
|
f010fc0c83 | ||
|
|
3da9221d43 | ||
|
|
a7f0f257b8 | ||
|
|
c095e22fdb | ||
|
|
0c98dff044 | ||
|
|
69cc56e4a7 | ||
|
|
a54e6daaa1 | ||
|
|
3009cacdb2 | ||
|
|
b9bacb9988 | ||
|
|
6905c6a131 | ||
|
|
ce7b94a878 | ||
|
|
c49cdb2e98 | ||
|
|
0ae4651a78 | ||
|
|
12d24800b8 | ||
|
|
c09eab71cc | ||
|
|
5bbafbdb3c | ||
|
|
157b63a5d0 | ||
|
|
341d39a6a3 | ||
|
|
bb8dad1607 | ||
|
|
20bcef1891 | ||
|
|
f9bf29e68c | ||
|
|
820e5af5fb | ||
|
|
681122bcca | ||
|
|
9418ea4385 | ||
|
|
73ab5e7391 | ||
|
|
bf6d07a726 | ||
|
|
e4849d6dfe | ||
|
|
2002fcb31b | ||
|
|
21c828051c | ||
|
|
c7e84e2249 | ||
|
|
a3645c286d | ||
|
|
618df98abf | ||
|
|
4bbf739786 | ||
|
|
878a145420 | ||
|
|
32bd44b19a | ||
|
|
c5ced6994a | ||
|
|
2929e74c78 | ||
|
|
0c01dcf2f5 | ||
|
|
9eacd35319 | ||
|
|
ca6af04eba | ||
|
|
691e3f1b82 | ||
|
|
80dbbd3d12 | ||
|
|
28d99c4c0f | ||
|
|
3f8012f0d0 | ||
|
|
a53de511ce | ||
|
|
34eb5900fb | ||
|
|
514f7efc6e | ||
|
|
de81ec5d75 | ||
|
|
4b23834af8 | ||
|
|
0e2be676db | ||
|
|
3be72b3c71 | ||
|
|
889ffaaa33 | ||
|
|
50515568d8 | ||
|
|
56f96291e4 | ||
|
|
f5db8cf7ce | ||
|
|
ea91647862 | ||
|
|
d7a5467f4f | ||
|
|
95db6655e7 | ||
|
|
36ec1e8683 | ||
|
|
1797db97a0 | ||
|
|
10964f0f51 | ||
|
|
d2733b704d | ||
|
|
b1401ee00b | ||
|
|
e71589a292 | ||
|
|
54f56e0e2c | ||
|
|
73543a5477 | ||
|
|
7ad92b1bbd | ||
|
|
a1e2592965 | ||
|
|
ff29100fd4 | ||
|
|
059356ccd8 | ||
|
|
5d0c7d28e4 | ||
|
|
75d0ca926f | ||
|
|
8582dc479b | ||
|
|
b7383d99dc | ||
|
|
d88f5728a4 | ||
|
|
6d66ee8653 | ||
|
|
ec21ea05bf | ||
|
|
bba517c8b7 | ||
|
|
ebcc4ccb06 | ||
|
|
656335b541 | ||
|
|
e39613f49d | ||
|
|
706d251ef4 | ||
|
|
80e17c739e | ||
|
|
69ca41c68d | ||
|
|
6f61c7d26d | ||
|
|
b4a08ce15e | ||
|
|
744ad7988a | ||
|
|
56cd6da556 | ||
|
|
42955af42c | ||
|
|
f20a9fd75b | ||
|
|
4942f23de6 | ||
|
|
037851a002 | ||
|
|
cf92c5635d | ||
|
|
5f7abbb2eb | ||
|
|
fdb02d10d6 | ||
|
|
0dc55fc449 | ||
|
|
790a550e7f | ||
|
|
d19a7538fd | ||
|
|
913cdfa984 | ||
|
|
289b2a53db | ||
|
|
5941f2952e | ||
|
|
414947cbb5 | ||
|
|
435e74f83a | ||
|
|
0ca5aefc12 | ||
|
|
938c641736 | ||
|
|
68b066a2d1 | ||
|
|
c5508fe9be | ||
|
|
418e863d26 | ||
|
|
1bb721edf5 | ||
|
|
801006fa70 | ||
|
|
1763dec981 | ||
|
|
128291a36a | ||
|
|
31ead537c9 | ||
|
|
25cfcc7cee | ||
|
|
e7a70f1e26 | ||
|
|
ad43ce4095 | ||
|
|
b1b00b5342 | ||
|
|
a4dc9af301 | ||
|
|
eefaab1752 | ||
|
|
c16dd1bba3 | ||
|
|
08df0ceae0 | ||
|
|
26de907185 | ||
|
|
e90800884a | ||
|
|
bebbba49ff | ||
|
|
d417a46e67 | ||
|
|
1b36154142 | ||
|
|
f4f64423a4 | ||
|
|
3ba2145cc9 | ||
|
|
9386544b3d | ||
|
|
3e3a83e6d4 | ||
|
|
33105a832f | ||
|
|
01f6a1d877 | ||
|
|
4143f1a8ce | ||
|
|
ea41c4b41a | ||
|
|
bd571702cb | ||
|
|
fa8764342e | ||
|
|
757f8f2762 | ||
|
|
0bd626d659 | ||
|
|
5db8d6ccbe | ||
|
|
eb30ef22fc | ||
|
|
2e4be0274a | ||
|
|
28af80873a | ||
|
|
8795ecc231 | ||
|
|
5bf20370eb | ||
|
|
893fc87b28 | ||
|
|
6e0f6dabe4 | ||
|
|
2983238ef5 | ||
|
|
3b86985ae3 | ||
|
|
732c87b013 | ||
|
|
6be9a14335 | ||
|
|
f93fae6696 | ||
|
|
373525b364 | ||
|
|
7b03ce4769 | ||
|
|
b484827c15 | ||
|
|
b5f6eb6526 | ||
|
|
e28d41ed0c | ||
|
|
aefb094694 | ||
|
|
cf1811cec3 | ||
|
|
273da833db | ||
|
|
f6916f093b | ||
|
|
51c1179326 | ||
|
|
6f0123e13e | ||
|
|
ca5b9e5e00 | ||
|
|
283cfc7c59 | ||
|
|
e05aaeba26 | ||
|
|
8d96ea7fcf | ||
|
|
b400669919 | ||
|
|
fc17fa41ff | ||
|
|
a214866b08 | ||
|
|
62e6978be1 | ||
|
|
00d70f8cc7 | ||
|
|
60cca03d31 | ||
|
|
52ae633c21 | ||
|
|
f02920721f | ||
|
|
765fadb0ed | ||
|
|
ccc90ccb67 | ||
|
|
bff3c45976 | ||
|
|
17308f9a66 | ||
|
|
ed80caf532 | ||
|
|
465f910812 | ||
|
|
a7c2fbba1f | ||
|
|
b7da4f2096 | ||
|
|
b73907bb84 | ||
|
|
499b086b57 | ||
|
|
b83f289013 | ||
|
|
3ea832bd03 | ||
|
|
1b43806d51 | ||
|
|
745d1460e5 | ||
|
|
15e7a6efda | ||
|
|
602682a2f4 | ||
|
|
2296198b44 | ||
|
|
cc3ef1c065 | ||
|
|
1d5d6004bc | ||
|
|
3345d56f5b | ||
|
|
e23216df07 | ||
|
|
3cab753398 | ||
|
|
5e71367c21 | ||
|
|
1eca1b60ab | ||
|
|
01c8130535 | ||
|
|
062cc6dbdc | ||
|
|
72bfcd3521 | ||
|
|
33cf81a76d | ||
|
|
445aa116ad | ||
|
|
bdc173187d | ||
|
|
87ad321987 | ||
|
|
19c6cff7d3 | ||
|
|
996c67beea | ||
|
|
e2fe0697ee | ||
|
|
0a5680da13 | ||
|
|
264f4d209f | ||
|
|
3ee69ea11a | ||
|
|
c523754516 | ||
|
|
13015e3da9 | ||
|
|
f7141dd0c9 | ||
|
|
3430776ffc | ||
|
|
48a46aea47 | ||
|
|
eb7f3b54ec | ||
|
|
c43d46c7e8 | ||
|
|
7d77d46fa2 | ||
|
|
934e809ac3 | ||
|
|
8081f22e92 | ||
|
|
c9b99dd67a | ||
|
|
fe83736ec7 | ||
|
|
817d2c1e9c | ||
|
|
c0d9b7c161 | ||
|
|
014ee22b79 | ||
|
|
cbd0898eb1 | ||
|
|
63d27b4a6f | ||
|
|
fdfa03eaaf | ||
|
|
c9d1129e93 | ||
|
|
376b485261 | ||
|
|
75fa008f0a | ||
|
|
54983913e1 | ||
|
|
c6f3c684a8 | ||
|
|
39aad231cd | ||
|
|
bd06c435e9 | ||
|
|
fb1caea4de | ||
|
|
d8430f4ca9 | ||
|
|
f7315d405d | ||
|
|
bcd251c4df | ||
|
|
3f963f3329 | ||
|
|
4117c461bb | ||
|
|
8c6ea947ba | ||
|
|
f2a3fc1da1 | ||
|
|
771ad2586a | ||
|
|
60a3956c07 | ||
|
|
d097846756 | ||
|
|
1209d1b269 | ||
|
|
209dea52b3 | ||
|
|
f954822d74 | ||
|
|
dbdb89cd10 | ||
|
|
c53e426989 | ||
|
|
484ad0ca38 | ||
|
|
ac7f24daff | ||
|
|
c1f724ac5e | ||
|
|
d5f643aab9 | ||
|
|
72fc065928 | ||
|
|
25b479fdb6 | ||
|
|
4b18ddfe79 | ||
|
|
bdea7de27e | ||
|
|
bcd9d5c9af | ||
|
|
fe976810e3 | ||
|
|
033167a451 | ||
|
|
cdfb9132ad | ||
|
|
b6d29a33d2 | ||
|
|
003d7b85ab | ||
|
|
1b585ea5c9 | ||
|
|
47a80dc603 | ||
|
|
b29d37a8be | ||
|
|
60369fc2a4 | ||
|
|
3927d823c0 | ||
|
|
1d58dfdc94 | ||
|
|
7ac75f7344 | ||
|
|
80c43056cc | ||
|
|
1564655777 | ||
|
|
2d243fc310 | ||
|
|
75ad5a5b4d | ||
|
|
5e69bf172c | ||
|
|
f307332409 | ||
|
|
56378d168b | ||
|
|
274be9d716 | ||
|
|
eec0760e44 | ||
|
|
6ed108e741 | ||
|
|
7429dc5b2d | ||
|
|
9a900a5bac | ||
|
|
ba4796c763 | ||
|
|
57f238d48e | ||
|
|
d93a73c29e | ||
|
|
94303cff93 | ||
|
|
a977cfe857 | ||
|
|
00912b277a | ||
|
|
40eff0ce0f | ||
|
|
9835d33d12 | ||
|
|
64165e387e | ||
|
|
34d94760a3 | ||
|
|
713990f57b | ||
|
|
58544d141d | ||
|
|
e125aa33d2 | ||
|
|
fd7977b35a | ||
|
|
dc4f0ac2d3 | ||
|
|
ee2f462ee9 | ||
|
|
c3cd1871ef | ||
|
|
3e4c3f10cf | ||
|
|
80852fd0ea | ||
|
|
ad7d23a8de | ||
|
|
166b035983 | ||
|
|
c27ac833d1 | ||
|
|
e62e9eb0bf | ||
|
|
b07bc3c456 | ||
|
|
ebdbe115dd | ||
|
|
5f6819afcb | ||
|
|
ae1df0b556 | ||
|
|
d3e3c3c95b | ||
|
|
adc80eabb0 | ||
|
|
42fa4d49d4 | ||
|
|
b077561a6f | ||
|
|
57bc1d5ccf | ||
|
|
62aa1fa5d7 | ||
|
|
73b4b2a46b | ||
|
|
76445146fb | ||
|
|
fcf234ef34 | ||
|
|
aa1e1fd79c | ||
|
|
446d6fe357 | ||
|
|
2a8c04b285 | ||
|
|
76879a2ff5 | ||
|
|
d527ec4974 | ||
|
|
74c7072926 | ||
|
|
ab472c04be | ||
|
|
150caeec40 | ||
|
|
761d0ca505 | ||
|
|
9160bfe452 | ||
|
|
942b436974 | ||
|
|
6ac9bbb19c | ||
|
|
047fe7ba27 | ||
|
|
74ce19052d | ||
|
|
5e8547063e | ||
|
|
80a8f8967f | ||
|
|
902d9ff13b | ||
|
|
96363136d2 |
17
.cirrus.yml
@@ -3,11 +3,10 @@ container:
|
|||||||
|
|
||||||
lint_task:
|
lint_task:
|
||||||
setup_script:
|
setup_script:
|
||||||
- pip install flake8
|
- pip install flake8 codespell
|
||||||
- pip install codespell
|
|
||||||
script:
|
script:
|
||||||
- flake8 --version
|
- flake8 --version
|
||||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
|
- flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
||||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
||||||
|
|
||||||
test_task:
|
test_task:
|
||||||
@@ -17,25 +16,21 @@ test_task:
|
|||||||
- BIN_DIR: /tmp/cached_bin
|
- BIN_DIR: /tmp/cached_bin
|
||||||
- PARTICL_BINDIR: ${BIN_DIR}/particl
|
- PARTICL_BINDIR: ${BIN_DIR}/particl
|
||||||
- BITCOIN_BINDIR: ${BIN_DIR}/bitcoin
|
- BITCOIN_BINDIR: ${BIN_DIR}/bitcoin
|
||||||
|
- BITCOINCASH_BINDIR: ${BIN_DIR}/bitcoincash
|
||||||
- LITECOIN_BINDIR: ${BIN_DIR}/litecoin
|
- LITECOIN_BINDIR: ${BIN_DIR}/litecoin
|
||||||
- XMR_BINDIR: ${BIN_DIR}/monero
|
- XMR_BINDIR: ${BIN_DIR}/monero
|
||||||
setup_script:
|
setup_script:
|
||||||
- apt-get update
|
- apt-get update
|
||||||
- apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config
|
- apt-get install -y python3-pip pkg-config
|
||||||
- pip install tox pytest
|
- pip install tox pytest
|
||||||
- python3 setup.py install
|
- pip install .
|
||||||
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip
|
|
||||||
- unzip -d coincurve-anonswap coincurve-anonswap.zip
|
|
||||||
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
|
||||||
- cd coincurve-anonswap
|
|
||||||
- python3 setup.py install --force
|
|
||||||
bins_cache:
|
bins_cache:
|
||||||
folder: /tmp/cached_bin
|
folder: /tmp/cached_bin
|
||||||
reupload_on_changes: false
|
reupload_on_changes: false
|
||||||
fingerprint_script:
|
fingerprint_script:
|
||||||
- basicswap-prepare -v
|
- basicswap-prepare -v
|
||||||
populate_script:
|
populate_script:
|
||||||
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,litecoin,monero
|
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,bitcoincash,litecoin,monero
|
||||||
script:
|
script:
|
||||||
- cd "${CIRRUS_WORKING_DIR}"
|
- cd "${CIRRUS_WORKING_DIR}"
|
||||||
- export DATADIRS="${TEST_DIR}"
|
- export DATADIRS="${TEST_DIR}"
|
||||||
|
|||||||
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "pip" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
open-pull-requests-limit: 20
|
||||||
|
target-branch: "dev"
|
||||||
71
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
BIN_DIR: /tmp/cached_bin
|
||||||
|
TEST_RELOAD_PATH: /tmp/test_basicswap
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.12"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v3
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install flake8 codespell pytest
|
||||||
|
pip install -r requirements.txt --require-hashes
|
||||||
|
- name: Install
|
||||||
|
run: |
|
||||||
|
pip install .
|
||||||
|
# Print the core versions to a file for caching
|
||||||
|
basicswap-prepare --version --withcoins=bitcoin | tail -n +2 > core_versions.txt
|
||||||
|
cat core_versions.txt
|
||||||
|
- name: Running flake8
|
||||||
|
run: |
|
||||||
|
flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
||||||
|
- name: Running codespell
|
||||||
|
run: |
|
||||||
|
codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
||||||
|
- name: Running test_other
|
||||||
|
run: |
|
||||||
|
pytest tests/basicswap/test_other.py
|
||||||
|
- name: Cache coin cores
|
||||||
|
id: cache-cores
|
||||||
|
uses: actions/cache@v3
|
||||||
|
env:
|
||||||
|
cache-name: cache-cores
|
||||||
|
with:
|
||||||
|
path: /tmp/cached_bin
|
||||||
|
key: cores-${{ runner.os }}-${{ hashFiles('**/core_versions.txt') }}
|
||||||
|
|
||||||
|
- if: ${{ steps.cache-cores.outputs.cache-hit != 'true' }}
|
||||||
|
name: Running basicswap-prepare
|
||||||
|
run: |
|
||||||
|
basicswap-prepare --bindir="$BIN_DIR" --preparebinonly --withcoins=particl,bitcoin,monero
|
||||||
|
- name: Running test_xmr
|
||||||
|
run: |
|
||||||
|
export PYTHONPATH=$(pwd)
|
||||||
|
export PARTICL_BINDIR="$BIN_DIR/particl"
|
||||||
|
export BITCOIN_BINDIR="$BIN_DIR/bitcoin"
|
||||||
|
export XMR_BINDIR="$BIN_DIR/monero"
|
||||||
|
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx"
|
||||||
|
- name: Running test_encrypted_xmr_reload
|
||||||
|
run: |
|
||||||
|
export PYTHONPATH=$(pwd)
|
||||||
|
export TEST_PATH=${TEST_RELOAD_PATH}
|
||||||
|
mkdir -p ${TEST_PATH}/bin
|
||||||
|
cp -r $BIN_DIR/* ${TEST_PATH}/bin/
|
||||||
|
pytest tests/basicswap/extended/test_encrypted_xmr_reload.py
|
||||||
3
.gitignore
vendored
@@ -13,3 +13,6 @@ __pycache__
|
|||||||
# geckodriver.log
|
# geckodriver.log
|
||||||
*.log
|
*.log
|
||||||
docker/.env
|
docker/.env
|
||||||
|
|
||||||
|
# vscode dev container settings
|
||||||
|
compose-dev.yaml
|
||||||
60
.travis.yml
@@ -1,60 +0,0 @@
|
|||||||
dist: bionic
|
|
||||||
os: linux
|
|
||||||
language: python
|
|
||||||
python: '3.7'
|
|
||||||
stages:
|
|
||||||
- lint
|
|
||||||
- test
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- TEST_DIR=${HOME}/test_basicswap2
|
|
||||||
- TEST_RELOAD_PATH=~/test_basicswap1
|
|
||||||
- BIN_DIR=~/cached_bin
|
|
||||||
- PARTICL_BINDIR=${BIN_DIR}/particl
|
|
||||||
- BITCOIN_BINDIR=${BIN_DIR}/bitcoin
|
|
||||||
- LITECOIN_BINDIR=${BIN_DIR}/litecoin
|
|
||||||
- XMR_BINDIR=${BIN_DIR}/monero
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- "$BIN_DIR"
|
|
||||||
before_install:
|
|
||||||
- sudo apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config
|
|
||||||
install:
|
|
||||||
- travis_retry pip install tox pytest
|
|
||||||
before_script:
|
|
||||||
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip
|
|
||||||
- unzip -d coincurve-anonswap coincurve-anonswap.zip
|
|
||||||
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
|
||||||
- cd coincurve-anonswap
|
|
||||||
- python3 setup.py install --force
|
|
||||||
script:
|
|
||||||
- cd $TRAVIS_BUILD_DIR
|
|
||||||
- python3 setup.py install
|
|
||||||
- basicswap-prepare --bindir=${BIN_DIR} --preparebinonly --withcoins=particl,bitcoin,litecoin,monero
|
|
||||||
- export DATADIRS="${TEST_DIR}"
|
|
||||||
- mkdir -p "${DATADIRS}/bin"
|
|
||||||
- cp -r ${BIN_DIR} "${DATADIRS}/bin"
|
|
||||||
- mkdir -p "${TEST_RELOAD_PATH}/bin"
|
|
||||||
- cp -r ${BIN_DIR} "${TEST_RELOAD_PATH}/bin"
|
|
||||||
- # tox
|
|
||||||
- pytest tests/basicswap/test_xmr.py
|
|
||||||
- pytest tests/basicswap/test_xmr_reload.py
|
|
||||||
- pytest tests/basicswap/test_xmr_bids_offline.py
|
|
||||||
after_success:
|
|
||||||
- echo "End test"
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: lint
|
|
||||||
env:
|
|
||||||
cache: false
|
|
||||||
install:
|
|
||||||
- travis_retry pip install flake8==3.7.0
|
|
||||||
- travis_retry pip install codespell==1.15.0
|
|
||||||
before_script:
|
|
||||||
script:
|
|
||||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
|
|
||||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
|
||||||
after_success:
|
|
||||||
- echo "End lint"
|
|
||||||
- stage: test
|
|
||||||
env:
|
|
||||||
21
Dockerfile
@@ -5,30 +5,15 @@ ENV LANG=C.UTF-8 \
|
|||||||
DATADIRS="/coindata"
|
DATADIRS="/coindata"
|
||||||
|
|
||||||
RUN apt-get update; \
|
RUN apt-get update; \
|
||||||
apt-get install -y wget python3-pip gnupg unzip make g++ autoconf automake libtool pkg-config gosu tzdata;
|
apt-get install -y --no-install-recommends \
|
||||||
|
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata cmake ninja-build;
|
||||||
# Must install protoc directly as latest package is only on 3.12
|
|
||||||
RUN wget -O protobuf_src.tar.gz https://github.com/protocolbuffers/protobuf/releases/download/v21.1/protobuf-python-4.21.1.tar.gz && \
|
|
||||||
tar xvf protobuf_src.tar.gz && \
|
|
||||||
cd protobuf-3.21.1 && \
|
|
||||||
./configure --prefix=/usr && \
|
|
||||||
make -j$(nproc) install && \
|
|
||||||
ldconfig
|
|
||||||
|
|
||||||
ARG COINCURVE_VERSION=v0.2
|
|
||||||
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
|
|
||||||
unzip coincurve-anonswap.zip && \
|
|
||||||
mv ./coincurve-anonswap_$COINCURVE_VERSION ./coincurve-anonswap && \
|
|
||||||
cd coincurve-anonswap && \
|
|
||||||
python3 setup.py install --force
|
|
||||||
|
|
||||||
# Install requirements first so as to skip in subsequent rebuilds
|
# Install requirements first so as to skip in subsequent rebuilds
|
||||||
COPY ./requirements.txt requirements.txt
|
COPY ./requirements.txt requirements.txt
|
||||||
RUN pip3 install -r requirements.txt
|
RUN pip3 install -r requirements.txt --require-hashes
|
||||||
|
|
||||||
COPY . basicswap-master
|
COPY . basicswap-master
|
||||||
RUN cd basicswap-master; \
|
RUN cd basicswap-master; \
|
||||||
protoc -I=basicswap --python_out=basicswap basicswap/messages.proto; \
|
|
||||||
pip3 install .;
|
pip3 install .;
|
||||||
|
|
||||||
RUN useradd -ms /bin/bash swap_user && \
|
RUN useradd -ms /bin/bash swap_user && \
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
include *.md LICENSE
|
|
||||||
|
|
||||||
recursive-include doc *
|
|
||||||
recursive-include basicswap/templates *
|
|
||||||
recursive-include basicswap/static *
|
|
||||||
76
README.md
@@ -15,27 +15,31 @@ Table of Contents
|
|||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
The BasicSwap DEX is a privacy-first and decentralized exchange which features cross-chain atomic swaps and a distributed order book.
|
**BasicSwap** is the world’s most secure and decentralized DEX. It facilitates cross-chain atomic swaps by enabling peers to interact directly with each other within a free and open environment without central points of failure.
|
||||||
|
|
||||||
[BasicSwap](https://academy.particl.io/en/latest/glossary.html#term-BasicSwap) is a cross-chain and privacy-centric DEX (decentralized exchange) that lets you trade cryptocurrencies with no third party involvement. Its distributed order book lets you make or take orders at no cost and trade within a free and open environment without central points of failure.
|
This DEX is fully non-custodial and features a decentralized order book, letting you create or accept swap offers without any fees, counterparties, or the need for accounts.
|
||||||
|
|
||||||
This DEX protocol was built in direct response to the increasingly invasive demands and data mining practices of today’s cryptocurrency exchanges. It strives to bring more decentralized and more private cryptocurrency trading conditions for all.
|
Built as a low-friction, highly secure solution to the frequent losses of funds on centralized exchanges (e.g., FTX, BitFinex, MtGox), **BasicSwap** aims to provide more reliable and secure cryptocurrency trading conditions for everyone.
|
||||||
|
|
||||||
BasicSwap is still in beta. This means that, while it already offers most of the vital trading features you’d expect to see on centralized exchanges, it is still in heavy development, and many more features will come about in the near future.
|
**BasicSwap** is currently in active development by the community. While it already offers some of the essential trading features you'd expect from an exchange, more features and quality-of-life improvements are being worked on with the goal to provide a smoother user experience.
|
||||||
|
|
||||||
Check out our [roadmap](https://basicswapdex.com/roadmap) to get a better idea of what we've got planned for it!
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* **True cross-chain support** — Swap cryptocurrencies that live on entirely different blockchain environments, like Bitcoin and Monero.
|
* **True cross-chain support** — Swap cryptocurrencies that live on entirely different blockchain environments, like Bitcoin and Monero.
|
||||||
* **Distributed order book** — Make or take limit orders on a completely distributed order book system.
|
* **Decentralized order book** — Make or take swap offers on a completely decentralized order book system.
|
||||||
* **No third-party or middleman** — Trade crypto with no intermediaries whatsoever.
|
* **No third-party or middleman** — Trade crypto with no intermediaries, completely eliminating central points of failure.
|
||||||
* **No trading fees** — Only pay the typical cryptocurrency network fee.
|
* **No trading fees** — Only pay the typical cryptocurrency network fee.
|
||||||
* **Privacy from the ground up** — Every component of BasicSwap is built with a privacy-first commitment.
|
* **Superior financial privacy** — Protect your financial information from unauthorized access with BasicSwap’s privacy-conscious technology.
|
||||||
* **Full Monero support** — Swap Monero with a variety of other cryptocurrencies like Bitcoin or Particl. No wrapped assets or trickery involved.
|
* **Full Monero support** — Swap Monero with a variety of other cryptocurrencies like Bitcoin or Particl. No wrapped assets or layer-2 involved.
|
||||||
* **User-friendly interface** — Enjoy all these features within a user-friendly and intuitive interface that handles all the complicated parts for you.
|
* **User-friendly interface** — Enjoy all these features within a user-friendly and intuitive interface that handles all the complicated parts for you.
|
||||||
|
|
||||||
BasicSwap is still in beta. This means that, while it already offers most of the vital trading features you’d expect to see on centralized exchanges, it is still in heavy development, and many more features will come about in the near future.
|
## Under the Hood
|
||||||
|
|
||||||
|
**BasicSwap** can be best understood as the decentralized version of the SWIFT messaging network; providing a decentralized messaging protocol that allows for peers to connect directly with each other with the purpose of executing atomic swaps without central points of failure and using official core wallets (Bitcoin Core, Litecoin Core, etc).
|
||||||
|
|
||||||
|
**BasicSwap** does not process, initiate, or execute swaps; it merely enables peers to communicate with each other and exchange the required information to simplify the process of using atomic swaps on the respective blockchains of the coins being swapped.
|
||||||
|
|
||||||
|
In essence, **BasicSwap** operates merely as a decentralized messaging protocol supplemented by a user-friendly interface.
|
||||||
|
|
||||||
## Available Assets
|
## Available Assets
|
||||||
|
|
||||||
@@ -60,6 +64,12 @@ BasicSwap is compatible with the following digital assets.
|
|||||||
<td>XMR
|
<td>XMR
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Bitcoin Cash
|
||||||
|
</td>
|
||||||
|
<td>BCH
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Dash
|
<td>Dash
|
||||||
</td>
|
</td>
|
||||||
@@ -84,47 +94,63 @@ BasicSwap is compatible with the following digital assets.
|
|||||||
<td>PIVX
|
<td>PIVX
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Decred
|
||||||
|
</td>
|
||||||
|
<td>DCR
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Wownero
|
||||||
|
</td>
|
||||||
|
<td>WOW
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Particl
|
<td>Particl
|
||||||
</td>
|
</td>
|
||||||
<td>PART
|
<td>PART
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Dogecoin
|
||||||
|
</td>
|
||||||
|
<td>DOGE
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Namecoin
|
||||||
|
</td>
|
||||||
|
<td>NMC
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
We plan on adding many other cryptocurrencies moving forward, including ETH and its ERC-20 tokens. However, due to the true cross-chain nature of the BasicSwap DEX protocol, each integration has to be done on a case-by-case basis.
|
If you’d like to add a cryptocurrency to BasicSwap, refer to how other cryptocurrencies have been integrated to the DEX by following [this link](https://academy.particl.io/en/latest/basicswap-guides/basicswapguides_apply.html).
|
||||||
|
|
||||||
If you’d like to add a cryptocurrency to BasicSwap, either [apply for a listing using our listing application form](https://forms.gle/9DsHoHTJVqSiMNHW9), or try coding the integration yourself by referencing how other cryptocurrencies have been added. Follow [this link](https://academy.particl.io/en/latest/basicswap-guides/basicswapguides_apply.html) for more information on how to integrate a coin yourself.
|
|
||||||
|
|
||||||
# Participate
|
# Participate
|
||||||
|
|
||||||
### Chats
|
### Chats
|
||||||
|
|
||||||
* **For developers** The chat [#particl-dev:matrix.org](https://matrix.to/#/#particl-dev:matrix.org) using a Matrix client.
|
* **For support** Join the community on [#basicswap:matrix.org](https://matrix.to/#/#basicswap:matrix.org) using a Matrix client.
|
||||||
* **For community** The community chat [https://discord.me/particl](https://discord.me/particl) [](https://discord.me/particl).
|
|
||||||
|
|
||||||
[](http://twitter.com/BasicSwapDEX)
|
[](http://twitter.com/BasicSwapDEX)
|
||||||
[](http://reddit.com/r/particl)
|
|
||||||
|
|
||||||
### Documentation, installation
|
### Documentation, installation
|
||||||
|
|
||||||
For non-developers curious to explore a new world of commerce, binaries can be downloaded and installed. It is the easiest way to get started. Following the guides on [Particl Academy](https://academy.particl.io), a reference book in straightforward language, is recommended.
|
Follow the guides on [Particl Academy](https://academy.particl.io) for tutorials and guides on how BasicSwap works.
|
||||||
|
|
||||||
* [Download BasicSwapDEX](https://github.com/tecnovert/basicswap/tree/master/doc)
|
* [Download BasicSwapDEX](https://github.com/basicswap/basicswap/tree/master/doc)
|
||||||
|
|
||||||
#### Community chat support
|
#### Community chat support
|
||||||
|
|
||||||
* [Discord](https://discord.me/particl) navigate to the #support channel
|
* [Matrix](https://matrix.to/#/#basicswap:matrix.org)
|
||||||
|
|
||||||
* [Telegram](https://t.me/particlhelp)
|
|
||||||
|
|
||||||
* [Matrix](https://matrix.to/#/#particlhelp:matrix.org)
|
|
||||||
|
|
||||||
# Tutorials
|
# Tutorials
|
||||||
|
|
||||||
You can find a wide variety of tutorials and step-by-step guides about BasicSwap on the [Particl Academy](https://academy.particl.io) or on Particl’s Youtube channel.
|
You can find a wide variety of tutorials and step-by-step guides about BasicSwap on the [Particl Academy](https://academy.particl.io) or on Particl’s Youtube channel.
|
||||||
|
|
||||||
If you encounter an issue or try to accomplish something not mentioned in any of the tutorials included in the links above, please join the community chat support channels; you’ll be sure to find help and support from our awesome community and open-source team there!
|
If you encounter an issue or try to accomplish something not mentioned in any of the tutorials included in the links above, please join the community chat support channel; you’ll be sure to find help and support from current contributors there!
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
name = "basicswap"
|
name = "basicswap"
|
||||||
|
|
||||||
__version__ = "0.13.1"
|
__version__ = "0.14.3"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -18,12 +19,18 @@ import subprocess
|
|||||||
|
|
||||||
from sockshandler import SocksiPyHandler
|
from sockshandler import SocksiPyHandler
|
||||||
|
|
||||||
|
from .db import (
|
||||||
|
DBMethods,
|
||||||
|
)
|
||||||
from .rpc import (
|
from .rpc import (
|
||||||
callrpc,
|
callrpc,
|
||||||
)
|
)
|
||||||
from .util import (
|
from .util import (
|
||||||
TemporaryError,
|
TemporaryError,
|
||||||
)
|
)
|
||||||
|
from .util.logging import (
|
||||||
|
BSXLogger,
|
||||||
|
)
|
||||||
from .chainparams import (
|
from .chainparams import (
|
||||||
Coins,
|
Coins,
|
||||||
chainparams,
|
chainparams,
|
||||||
@@ -34,8 +41,8 @@ def getaddrinfo_tor(*args):
|
|||||||
return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (args[0], args[1]))]
|
return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (args[0], args[1]))]
|
||||||
|
|
||||||
|
|
||||||
class BaseApp:
|
class BaseApp(DBMethods):
|
||||||
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
|
def __init__(self, fp, data_dir, settings, chain, log_name="BasicSwap"):
|
||||||
self.log_name = log_name
|
self.log_name = log_name
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.fail_code = 0
|
self.fail_code = 0
|
||||||
@@ -46,20 +53,20 @@ class BaseApp:
|
|||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.coin_clients = {}
|
self.coin_clients = {}
|
||||||
self.coin_interfaces = {}
|
self.coin_interfaces = {}
|
||||||
self.mxDB = threading.RLock()
|
self.mxDB = threading.Lock()
|
||||||
self.debug = self.settings.get('debug', False)
|
self.debug = self.settings.get("debug", False)
|
||||||
self.delay_event = threading.Event()
|
self.delay_event = threading.Event()
|
||||||
self.chainstate_delay_event = threading.Event()
|
self.chainstate_delay_event = threading.Event()
|
||||||
|
|
||||||
self._network = None
|
self._network = None
|
||||||
self.prepareLogging()
|
self.prepareLogging()
|
||||||
self.log.info('Network: {}'.format(self.chain))
|
self.log.info("Network: {}".format(self.chain))
|
||||||
|
|
||||||
self.use_tor_proxy = self.settings.get('use_tor', False)
|
self.use_tor_proxy = self.settings.get("use_tor", False)
|
||||||
self.tor_proxy_host = self.settings.get('tor_proxy_host', '127.0.0.1')
|
self.tor_proxy_host = self.settings.get("tor_proxy_host", "127.0.0.1")
|
||||||
self.tor_proxy_port = self.settings.get('tor_proxy_port', 9050)
|
self.tor_proxy_port = self.settings.get("tor_proxy_port", 9050)
|
||||||
self.tor_control_password = self.settings.get('tor_control_password', None)
|
self.tor_control_password = self.settings.get("tor_control_password", None)
|
||||||
self.tor_control_port = self.settings.get('tor_control_port', 9051)
|
self.tor_control_port = self.settings.get("tor_control_port", 9051)
|
||||||
self.default_socket = socket.socket
|
self.default_socket = socket.socket
|
||||||
self.default_socket_timeout = socket.getdefaulttimeout()
|
self.default_socket_timeout = socket.getdefaulttimeout()
|
||||||
self.default_socket_getaddrinfo = socket.getaddrinfo
|
self.default_socket_getaddrinfo = socket.getaddrinfo
|
||||||
@@ -71,16 +78,24 @@ class BaseApp:
|
|||||||
self.delay_event.set()
|
self.delay_event.set()
|
||||||
|
|
||||||
def prepareLogging(self):
|
def prepareLogging(self):
|
||||||
|
logging.setLoggerClass(BSXLogger)
|
||||||
self.log = logging.getLogger(self.log_name)
|
self.log = logging.getLogger(self.log_name)
|
||||||
self.log.propagate = False
|
self.log.propagate = False
|
||||||
|
|
||||||
# Remove any existing handlers
|
# Remove any existing handlers
|
||||||
self.log.handlers = []
|
self.log.handlers = []
|
||||||
|
|
||||||
formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s', '%Y-%m-%d %H:%M:%S')
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s %(levelname)s : %(message)s", "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
stream_stdout = logging.StreamHandler()
|
stream_stdout = logging.StreamHandler()
|
||||||
if self.log_name != 'BasicSwap':
|
if self.log_name != "BasicSwap":
|
||||||
stream_stdout.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s : %(message)s', '%Y-%m-%d %H:%M:%S'))
|
stream_stdout.setFormatter(
|
||||||
|
logging.Formatter(
|
||||||
|
"%(asctime)s %(name)s %(levelname)s : %(message)s",
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
stream_stdout.setFormatter(formatter)
|
stream_stdout.setFormatter(formatter)
|
||||||
stream_fp = logging.StreamHandler(self.fp)
|
stream_fp = logging.StreamHandler(self.fp)
|
||||||
@@ -92,67 +107,91 @@ class BaseApp:
|
|||||||
|
|
||||||
def getChainClientSettings(self, coin):
|
def getChainClientSettings(self, coin):
|
||||||
try:
|
try:
|
||||||
return self.settings['chainclients'][chainparams[coin]['name']]
|
return self.settings["chainclients"][chainparams[coin]["name"]]
|
||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def setDaemonPID(self, name, pid) -> None:
|
def setDaemonPID(self, name, pid) -> None:
|
||||||
if isinstance(name, Coins):
|
if isinstance(name, Coins):
|
||||||
self.coin_clients[name]['pid'] = pid
|
self.coin_clients[name]["pid"] = pid
|
||||||
return
|
return
|
||||||
for c, v in self.coin_clients.items():
|
for c, v in self.coin_clients.items():
|
||||||
if v['name'] == name:
|
if v["name"] == name:
|
||||||
v['pid'] = pid
|
v["pid"] = pid
|
||||||
|
|
||||||
def getChainDatadirPath(self, coin) -> str:
|
def getChainDatadirPath(self, coin) -> str:
|
||||||
datadir = self.coin_clients[coin]['datadir']
|
datadir = self.coin_clients[coin]["datadir"]
|
||||||
testnet_name = '' if self.chain == 'mainnet' else chainparams[coin][self.chain].get('name', self.chain)
|
testnet_name = (
|
||||||
|
""
|
||||||
|
if self.chain == "mainnet"
|
||||||
|
else chainparams[coin][self.chain].get("name", self.chain)
|
||||||
|
)
|
||||||
return os.path.join(datadir, testnet_name)
|
return os.path.join(datadir, testnet_name)
|
||||||
|
|
||||||
def getCoinIdFromName(self, coin_name: str):
|
def getCoinIdFromName(self, coin_name: str):
|
||||||
for c, params in chainparams.items():
|
for c, params in chainparams.items():
|
||||||
if coin_name.lower() == params['name'].lower():
|
if coin_name.lower() == params["name"].lower():
|
||||||
return c
|
return c
|
||||||
raise ValueError('Unknown coin: {}'.format(coin_name))
|
raise ValueError("Unknown coin: {}".format(coin_name))
|
||||||
|
|
||||||
def callrpc(self, method, params=[], wallet=None):
|
def callrpc(self, method, params=[], wallet=None):
|
||||||
cc = self.coin_clients[Coins.PART]
|
cc = self.coin_clients[Coins.PART]
|
||||||
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])
|
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):
|
||||||
cc = self.coin_clients[coin]
|
cc = self.coin_clients[coin]
|
||||||
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])
|
return callrpc(
|
||||||
|
cc["rpcport"], cc["rpcauth"], method, params, wallet, cc["rpchost"]
|
||||||
|
)
|
||||||
|
|
||||||
def callcoincli(self, coin_type, params, wallet=None, timeout=None):
|
def callcoincli(self, coin_type, params, wallet=None, timeout=None):
|
||||||
bindir = self.coin_clients[coin_type]['bindir']
|
bindir = self.coin_clients[coin_type]["bindir"]
|
||||||
datadir = self.coin_clients[coin_type]['datadir']
|
datadir = self.coin_clients[coin_type]["datadir"]
|
||||||
command_cli = os.path.join(bindir, chainparams[coin_type]['name'] + '-cli' + ('.exe' if os.name == 'nt' else ''))
|
cli_bin: str = chainparams[coin_type].get(
|
||||||
args = [command_cli, ]
|
"cli_binname", chainparams[coin_type]["name"] + "-cli"
|
||||||
if self.chain != 'mainnet':
|
)
|
||||||
args.append('-' + self.chain)
|
command_cli = os.path.join(
|
||||||
args.append('-datadir=' + datadir)
|
bindir, cli_bin + (".exe" if os.name == "nt" else "")
|
||||||
|
)
|
||||||
|
args = [
|
||||||
|
command_cli,
|
||||||
|
]
|
||||||
|
if self.chain != "mainnet":
|
||||||
|
args.append("-" + self.chain)
|
||||||
|
args.append("-datadir=" + datadir)
|
||||||
args += shlex.split(params)
|
args += shlex.split(params)
|
||||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
p = subprocess.Popen(
|
||||||
|
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
out = p.communicate(timeout=timeout)
|
out = p.communicate(timeout=timeout)
|
||||||
if len(out[1]) > 0:
|
if len(out[1]) > 0:
|
||||||
raise ValueError('CLI error ' + str(out[1]))
|
raise ValueError("CLI error " + str(out[1]))
|
||||||
return out[0].decode('utf-8').strip()
|
return out[0].decode("utf-8").strip()
|
||||||
|
|
||||||
def is_transient_error(self, ex) -> bool:
|
def is_transient_error(self, ex) -> bool:
|
||||||
if isinstance(ex, TemporaryError):
|
if isinstance(ex, TemporaryError):
|
||||||
return True
|
return True
|
||||||
str_error = str(ex).lower()
|
str_error = str(ex).lower()
|
||||||
return 'read timed out' in str_error or 'no connection to daemon' in str_error
|
return "read timed out" in str_error or "no connection to daemon" in str_error
|
||||||
|
|
||||||
def setConnectionParameters(self, timeout=120):
|
def setConnectionParameters(self, timeout=120):
|
||||||
opener = urllib.request.build_opener()
|
opener = urllib.request.build_opener()
|
||||||
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
|
opener.addheaders = [("User-agent", "Mozilla/5.0")]
|
||||||
urllib.request.install_opener(opener)
|
urllib.request.install_opener(opener)
|
||||||
|
|
||||||
if self.use_tor_proxy:
|
if self.use_tor_proxy:
|
||||||
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port, rdns=True)
|
socks.setdefaultproxy(
|
||||||
|
socks.PROXY_TYPE_SOCKS5,
|
||||||
|
self.tor_proxy_host,
|
||||||
|
self.tor_proxy_port,
|
||||||
|
rdns=True,
|
||||||
|
)
|
||||||
socket.socket = socks.socksocket
|
socket.socket = socks.socksocket
|
||||||
socket.getaddrinfo = getaddrinfo_tor # Without this accessing .onion links would fail
|
socket.getaddrinfo = (
|
||||||
|
getaddrinfo_tor # Without this accessing .onion links would fail
|
||||||
|
)
|
||||||
|
|
||||||
socket.setdefaulttimeout(timeout)
|
socket.setdefaulttimeout(timeout)
|
||||||
|
|
||||||
@@ -162,12 +201,19 @@ class BaseApp:
|
|||||||
socket.getaddrinfo = self.default_socket_getaddrinfo
|
socket.getaddrinfo = self.default_socket_getaddrinfo
|
||||||
socket.setdefaulttimeout(self.default_socket_timeout)
|
socket.setdefaulttimeout(self.default_socket_timeout)
|
||||||
|
|
||||||
def readURL(self, url: str, timeout: int = 120, headers=None) -> bytes:
|
def readURL(self, url: str, timeout: int = 120, headers={}) -> bytes:
|
||||||
open_handler = None
|
open_handler = None
|
||||||
if self.use_tor_proxy:
|
if self.use_tor_proxy:
|
||||||
open_handler = SocksiPyHandler(socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port)
|
open_handler = SocksiPyHandler(
|
||||||
opener = urllib.request.build_opener(open_handler) if self.use_tor_proxy else urllib.request.build_opener()
|
socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port
|
||||||
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
|
)
|
||||||
|
opener = (
|
||||||
|
urllib.request.build_opener(open_handler)
|
||||||
|
if self.use_tor_proxy
|
||||||
|
else urllib.request.build_opener()
|
||||||
|
)
|
||||||
|
if headers is None:
|
||||||
|
opener.addheaders = [("User-agent", "Mozilla/5.0")]
|
||||||
request = urllib.request.Request(url, headers=headers)
|
request = urllib.request.Request(url, headers=headers)
|
||||||
return opener.open(request, timeout=timeout).read()
|
return opener.open(request, timeout=timeout).read()
|
||||||
|
|
||||||
@@ -178,7 +224,9 @@ class BaseApp:
|
|||||||
|
|
||||||
def torControl(self, query):
|
def torControl(self, query):
|
||||||
try:
|
try:
|
||||||
command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(self.tor_control_password, query).encode('utf-8')
|
command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(
|
||||||
|
self.tor_control_password, query
|
||||||
|
).encode("utf-8")
|
||||||
c = socket.create_connection((self.tor_proxy_host, self.tor_control_port))
|
c = socket.create_connection((self.tor_proxy_host, self.tor_control_port))
|
||||||
c.send(command)
|
c.send(command)
|
||||||
response = bytearray()
|
response = bytearray()
|
||||||
@@ -190,23 +238,23 @@ class BaseApp:
|
|||||||
c.close()
|
c.close()
|
||||||
return response
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(f'torControl {e}')
|
self.log.error(f"torControl {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
def getTime(self) -> int:
|
def getTime(self) -> int:
|
||||||
return int(time.time()) + self.mock_time_offset
|
return int(time.time()) + self.mock_time_offset
|
||||||
|
|
||||||
def setMockTimeOffset(self, new_offset: int) -> None:
|
def setMockTimeOffset(self, new_offset: int) -> None:
|
||||||
self.log.warning(f'Setting mocktime to {new_offset}')
|
self.log.warning(f"Setting mocktime to {new_offset}")
|
||||||
self.mock_time_offset = new_offset
|
self.mock_time_offset = new_offset
|
||||||
|
|
||||||
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
|
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
|
||||||
value: int = self.settings.get(name, default_v)
|
value: int = self.settings.get(name, default_v)
|
||||||
if value < min_v:
|
if value < min_v:
|
||||||
self.log.warning(f'Setting {name} to {min_v}')
|
self.log.warning(f"Setting {name} to {min_v}")
|
||||||
value = min_v
|
value = min_v
|
||||||
if value > max_v:
|
if value > max_v:
|
||||||
self.log.warning(f'Setting {name} to {max_v}')
|
self.log.warning(f"Setting {name} to {max_v}")
|
||||||
value = max_v
|
value = max_v
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021-2024 tecnovert
|
# Copyright (c) 2021-2024 tecnovert
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# 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,6 +65,7 @@ class SwapTypes(IntEnum):
|
|||||||
SELLER_FIRST_2MSG = auto()
|
SELLER_FIRST_2MSG = auto()
|
||||||
BUYER_FIRST_2MSG = auto()
|
BUYER_FIRST_2MSG = auto()
|
||||||
XMR_SWAP = auto()
|
XMR_SWAP = auto()
|
||||||
|
XMR_BCH_SWAP = auto()
|
||||||
|
|
||||||
|
|
||||||
class OfferStates(IntEnum):
|
class OfferStates(IntEnum):
|
||||||
@@ -105,6 +107,8 @@ class BidStates(IntEnum):
|
|||||||
BID_REQUEST_SENT = 29
|
BID_REQUEST_SENT = 29
|
||||||
BID_REQUEST_ACCEPTED = 30
|
BID_REQUEST_ACCEPTED = 30
|
||||||
BID_EXPIRED = 31
|
BID_EXPIRED = 31
|
||||||
|
BID_AACCEPT_DELAY = 32
|
||||||
|
BID_AACCEPT_FAIL = 33
|
||||||
|
|
||||||
|
|
||||||
class TxStates(IntEnum):
|
class TxStates(IntEnum):
|
||||||
@@ -136,6 +140,8 @@ class TxTypes(IntEnum):
|
|||||||
|
|
||||||
ITX_PRE_FUNDED = auto()
|
ITX_PRE_FUNDED = auto()
|
||||||
|
|
||||||
|
BCH_MERCY = auto()
|
||||||
|
|
||||||
|
|
||||||
class ActionTypes(IntEnum):
|
class ActionTypes(IntEnum):
|
||||||
ACCEPT_BID = auto()
|
ACCEPT_BID = auto()
|
||||||
@@ -183,6 +189,8 @@ class EventLogTypes(IntEnum):
|
|||||||
PTX_REDEEM_PUBLISHED = auto()
|
PTX_REDEEM_PUBLISHED = auto()
|
||||||
PTX_REFUND_PUBLISHED = auto()
|
PTX_REFUND_PUBLISHED = auto()
|
||||||
LOCK_TX_B_IN_MEMPOOL = auto()
|
LOCK_TX_B_IN_MEMPOOL = auto()
|
||||||
|
BCH_MERCY_TX_PUBLISHED = auto()
|
||||||
|
BCH_MERCY_TX_FOUND = auto()
|
||||||
|
|
||||||
|
|
||||||
class XmrSplitMsgTypes(IntEnum):
|
class XmrSplitMsgTypes(IntEnum):
|
||||||
@@ -194,6 +202,7 @@ class DebugTypes(IntEnum):
|
|||||||
NONE = 0
|
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()
|
||||||
|
BID_DONT_SPEND_COIN_A_LOCK_REFUND2 = auto() # continues
|
||||||
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()
|
||||||
@@ -204,6 +213,10 @@ class DebugTypes(IntEnum):
|
|||||||
DUPLICATE_ACTIONS = auto()
|
DUPLICATE_ACTIONS = auto()
|
||||||
DONT_CONFIRM_PTX = auto()
|
DONT_CONFIRM_PTX = auto()
|
||||||
OFFER_LOCK_2_VALUE_INC = auto()
|
OFFER_LOCK_2_VALUE_INC = auto()
|
||||||
|
BID_STOP_AFTER_COIN_B_LOCK = auto()
|
||||||
|
BID_DONT_SPEND_COIN_B_LOCK = auto()
|
||||||
|
WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND = auto()
|
||||||
|
BID_DONT_SPEND_COIN_A_LOCK = auto()
|
||||||
|
|
||||||
|
|
||||||
class NotificationTypes(IntEnum):
|
class NotificationTypes(IntEnum):
|
||||||
@@ -221,12 +234,12 @@ class AutomationOverrideOptions(IntEnum):
|
|||||||
|
|
||||||
def strAutomationOverrideOption(option):
|
def strAutomationOverrideOption(option):
|
||||||
if option == AutomationOverrideOptions.DEFAULT:
|
if option == AutomationOverrideOptions.DEFAULT:
|
||||||
return 'Default'
|
return "Default"
|
||||||
if option == AutomationOverrideOptions.ALWAYS_ACCEPT:
|
if option == AutomationOverrideOptions.ALWAYS_ACCEPT:
|
||||||
return 'Always Accept'
|
return "Always Accept"
|
||||||
if option == AutomationOverrideOptions.NEVER_ACCEPT:
|
if option == AutomationOverrideOptions.NEVER_ACCEPT:
|
||||||
return 'Never Accept'
|
return "Never Accept"
|
||||||
return 'Unknown'
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
class VisibilityOverrideOptions(IntEnum):
|
class VisibilityOverrideOptions(IntEnum):
|
||||||
@@ -237,245 +250,258 @@ class VisibilityOverrideOptions(IntEnum):
|
|||||||
|
|
||||||
def strVisibilityOverrideOption(option):
|
def strVisibilityOverrideOption(option):
|
||||||
if option == VisibilityOverrideOptions.DEFAULT:
|
if option == VisibilityOverrideOptions.DEFAULT:
|
||||||
return 'Default'
|
return "Default"
|
||||||
if option == VisibilityOverrideOptions.HIDE:
|
if option == VisibilityOverrideOptions.HIDE:
|
||||||
return 'Hide'
|
return "Hide"
|
||||||
if option == VisibilityOverrideOptions.BLOCK:
|
if option == VisibilityOverrideOptions.BLOCK:
|
||||||
return 'Block'
|
return "Block"
|
||||||
return 'Unknown'
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
def strOfferState(state):
|
def strOfferState(state):
|
||||||
if state == OfferStates.OFFER_SENT:
|
if state == OfferStates.OFFER_SENT:
|
||||||
return 'Sent'
|
return "Sent"
|
||||||
if state == OfferStates.OFFER_RECEIVED:
|
if state == OfferStates.OFFER_RECEIVED:
|
||||||
return 'Received'
|
return "Received"
|
||||||
if state == OfferStates.OFFER_ABANDONED:
|
if state == OfferStates.OFFER_ABANDONED:
|
||||||
return 'Abandoned'
|
return "Abandoned"
|
||||||
if state == OfferStates.OFFER_EXPIRED:
|
if state == OfferStates.OFFER_EXPIRED:
|
||||||
return 'Expired'
|
return "Expired"
|
||||||
return 'Unknown'
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
def strBidState(state):
|
def strBidState(state):
|
||||||
if state == BidStates.BID_SENT:
|
if state == BidStates.BID_SENT:
|
||||||
return 'Sent'
|
return "Sent"
|
||||||
if state == BidStates.BID_RECEIVING:
|
if state == BidStates.BID_RECEIVING:
|
||||||
return 'Receiving'
|
return "Receiving"
|
||||||
if state == BidStates.BID_RECEIVING_ACC:
|
if state == BidStates.BID_RECEIVING_ACC:
|
||||||
return 'Receiving accept'
|
return "Receiving accept"
|
||||||
if state == BidStates.BID_RECEIVED:
|
if state == BidStates.BID_RECEIVED:
|
||||||
return 'Received'
|
return "Received"
|
||||||
if state == BidStates.BID_ACCEPTED:
|
if state == BidStates.BID_ACCEPTED:
|
||||||
return 'Accepted'
|
return "Accepted"
|
||||||
if state == BidStates.SWAP_INITIATED:
|
if state == BidStates.SWAP_INITIATED:
|
||||||
return 'Initiated'
|
return "Initiated"
|
||||||
if state == BidStates.SWAP_PARTICIPATING:
|
if state == BidStates.SWAP_PARTICIPATING:
|
||||||
return 'Participating'
|
return "Participating"
|
||||||
if state == BidStates.SWAP_COMPLETED:
|
if state == BidStates.SWAP_COMPLETED:
|
||||||
return 'Completed'
|
return "Completed"
|
||||||
if state == BidStates.SWAP_TIMEDOUT:
|
if state == BidStates.SWAP_TIMEDOUT:
|
||||||
return 'Timed-out'
|
return "Timed-out"
|
||||||
if state == BidStates.BID_ABANDONED:
|
if state == BidStates.BID_ABANDONED:
|
||||||
return 'Abandoned'
|
return "Abandoned"
|
||||||
if state == BidStates.BID_STALLED_FOR_TEST:
|
if state == BidStates.BID_STALLED_FOR_TEST:
|
||||||
return 'Stalled (debug)'
|
return "Stalled (debug)"
|
||||||
if state == BidStates.BID_ERROR:
|
if state == BidStates.BID_ERROR:
|
||||||
return 'Error'
|
return "Error"
|
||||||
if state == BidStates.BID_REJECTED:
|
if state == BidStates.BID_REJECTED:
|
||||||
return 'Rejected'
|
return "Rejected"
|
||||||
if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
|
if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
|
||||||
return 'Script coin locked'
|
return "Script coin locked"
|
||||||
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
|
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
|
||||||
return 'Script coin spend tx valid'
|
return "Script coin spend tx valid"
|
||||||
if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
|
if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED:
|
||||||
return 'Scriptless coin locked'
|
return "Scriptless coin locked"
|
||||||
if state == BidStates.XMR_SWAP_LOCK_RELEASED:
|
if state == BidStates.XMR_SWAP_LOCK_RELEASED:
|
||||||
return 'Script coin lock released'
|
return "Script coin lock released"
|
||||||
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
||||||
return 'Script tx redeemed'
|
return "Script tx redeemed"
|
||||||
if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND:
|
if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND:
|
||||||
return 'Script pre-refund tx in chain'
|
return "Script pre-refund tx in chain"
|
||||||
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
|
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
|
||||||
return 'Scriptless tx redeemed'
|
return "Scriptless tx redeemed"
|
||||||
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
|
if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
|
||||||
return 'Scriptless tx recovered'
|
return "Scriptless tx recovered"
|
||||||
if state == BidStates.XMR_SWAP_FAILED_REFUNDED:
|
if state == BidStates.XMR_SWAP_FAILED_REFUNDED:
|
||||||
return 'Failed, refunded'
|
return "Failed, refunded"
|
||||||
if state == BidStates.XMR_SWAP_FAILED_SWIPED:
|
if state == BidStates.XMR_SWAP_FAILED_SWIPED:
|
||||||
return 'Failed, swiped'
|
return "Failed, swiped"
|
||||||
if state == BidStates.XMR_SWAP_FAILED:
|
if state == BidStates.XMR_SWAP_FAILED:
|
||||||
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:
|
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS:
|
||||||
return 'Exchanged script lock tx sigs msg'
|
return "Exchanged script lock tx sigs msg"
|
||||||
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
|
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
|
||||||
return 'Exchanged script lock spend tx msg'
|
return "Exchanged script lock spend tx msg"
|
||||||
if state == BidStates.BID_REQUEST_SENT:
|
if state == BidStates.BID_REQUEST_SENT:
|
||||||
return 'Request sent'
|
return "Request sent"
|
||||||
if state == BidStates.BID_REQUEST_ACCEPTED:
|
if state == BidStates.BID_REQUEST_ACCEPTED:
|
||||||
return 'Request accepted'
|
return "Request accepted"
|
||||||
if state == BidStates.BID_STATE_UNKNOWN:
|
if state == BidStates.BID_STATE_UNKNOWN:
|
||||||
return 'Unknown bid state'
|
return "Unknown bid state"
|
||||||
if state == BidStates.BID_EXPIRED:
|
if state == BidStates.BID_EXPIRED:
|
||||||
return 'Expired'
|
return "Expired"
|
||||||
return 'Unknown' + ' ' + str(state)
|
if state == BidStates.BID_AACCEPT_DELAY:
|
||||||
|
return "Auto accept delay"
|
||||||
|
if state == BidStates.BID_AACCEPT_FAIL:
|
||||||
|
return "Auto accept failed"
|
||||||
|
return "Unknown" + " " + str(state)
|
||||||
|
|
||||||
|
|
||||||
def strTxState(state):
|
def strTxState(state):
|
||||||
if state == TxStates.TX_NONE:
|
if state == TxStates.TX_NONE:
|
||||||
return 'None'
|
return "None"
|
||||||
if state == TxStates.TX_SENT:
|
if state == TxStates.TX_SENT:
|
||||||
return 'Sent'
|
return "Sent"
|
||||||
if state == TxStates.TX_CONFIRMED:
|
if state == TxStates.TX_CONFIRMED:
|
||||||
return 'Confirmed'
|
return "Confirmed"
|
||||||
if state == TxStates.TX_REDEEMED:
|
if state == TxStates.TX_REDEEMED:
|
||||||
return 'Redeemed'
|
return "Redeemed"
|
||||||
if state == TxStates.TX_REFUNDED:
|
if state == TxStates.TX_REFUNDED:
|
||||||
return 'Refunded'
|
return "Refunded"
|
||||||
if state == TxStates.TX_IN_MEMPOOL:
|
if state == TxStates.TX_IN_MEMPOOL:
|
||||||
return 'In Mempool'
|
return "In Mempool"
|
||||||
if state == TxStates.TX_IN_CHAIN:
|
if state == TxStates.TX_IN_CHAIN:
|
||||||
return 'In Chain'
|
return "In Chain"
|
||||||
return 'Unknown'
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
def strTxType(tx_type):
|
def strTxType(tx_type):
|
||||||
if tx_type == TxTypes.XMR_SWAP_A_LOCK:
|
if tx_type == TxTypes.XMR_SWAP_A_LOCK:
|
||||||
return 'Chain A Lock Tx'
|
return "Chain A Lock Tx"
|
||||||
if tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND:
|
if tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND:
|
||||||
return 'Chain A Lock Spend Tx'
|
return "Chain A Lock Spend Tx"
|
||||||
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND:
|
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND:
|
||||||
return 'Chain A Lock Refund Tx'
|
return "Chain A Lock Refund Tx"
|
||||||
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND:
|
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND:
|
||||||
return 'Chain A Lock Refund Spend Tx'
|
return "Chain A Lock Refund Spend Tx"
|
||||||
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE:
|
if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE:
|
||||||
return 'Chain A Lock Refund Swipe Tx'
|
return "Chain A Lock Refund Swipe Tx"
|
||||||
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
if tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
||||||
return 'Chain B Lock Tx'
|
return "Chain B Lock Tx"
|
||||||
if tx_type == TxTypes.ITX_PRE_FUNDED:
|
if tx_type == TxTypes.ITX_PRE_FUNDED:
|
||||||
return 'Funded mock initiate tx'
|
return "Funded mock initiate Tx"
|
||||||
return 'Unknown'
|
if tx_type == TxTypes.BCH_MERCY:
|
||||||
|
return "BCH Mercy Tx"
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
def strAddressType(addr_type):
|
def strAddressType(addr_type):
|
||||||
if addr_type == AddressTypes.OFFER:
|
if addr_type == AddressTypes.OFFER:
|
||||||
return 'Offer'
|
return "Offer"
|
||||||
if addr_type == AddressTypes.BID:
|
if addr_type == AddressTypes.BID:
|
||||||
return 'Bid'
|
return "Bid"
|
||||||
if addr_type == AddressTypes.RECV_OFFER:
|
if addr_type == AddressTypes.RECV_OFFER:
|
||||||
return 'Offer recv'
|
return "Offer recv"
|
||||||
if addr_type == AddressTypes.SEND_OFFER:
|
if addr_type == AddressTypes.SEND_OFFER:
|
||||||
return 'Offer send'
|
return "Offer send"
|
||||||
return 'Unknown'
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
def getLockName(lock_type):
|
def getLockName(lock_type):
|
||||||
if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
||||||
return 'Sequence lock, blocks'
|
return "Sequence lock, blocks"
|
||||||
if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
|
if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
|
||||||
return 'Sequence lock, time'
|
return "Sequence lock, time"
|
||||||
if lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
|
if lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
|
||||||
return 'blocks'
|
return "blocks"
|
||||||
if lock_type == TxLockTypes.ABS_LOCK_TIME:
|
if lock_type == TxLockTypes.ABS_LOCK_TIME:
|
||||||
return 'time'
|
return "time"
|
||||||
|
|
||||||
|
|
||||||
def describeEventEntry(event_type, event_msg):
|
def describeEventEntry(event_type, event_msg):
|
||||||
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
|
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
|
||||||
return 'Failed to publish lock tx B'
|
return "Failed to publish lock tx B"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED:
|
||||||
return 'Lock tx A published'
|
return "Lock tx A published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED:
|
||||||
return 'Lock tx B published'
|
return "Lock tx B published"
|
||||||
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
|
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
|
||||||
return 'Failed to publish lock tx B spend: ' + event_msg
|
return "Failed to publish lock tx B spend: " + event_msg
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
|
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
|
||||||
return 'Lock tx A seen in chain'
|
return "Lock tx A seen in chain"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
|
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
|
||||||
return 'Lock tx A confirmed in chain'
|
return "Lock tx A confirmed in chain"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_SEEN:
|
if event_type == EventLogTypes.LOCK_TX_B_SEEN:
|
||||||
return 'Lock tx B seen in chain'
|
return "Lock tx B seen in chain"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
|
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
|
||||||
return 'Lock tx B confirmed in chain'
|
return "Lock tx B confirmed in chain"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_IN_MEMPOOL:
|
if event_type == EventLogTypes.LOCK_TX_B_IN_MEMPOOL:
|
||||||
return 'Lock tx B seen in mempool'
|
return "Lock tx B seen in mempool"
|
||||||
if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED:
|
if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED:
|
||||||
return 'Debug tweak applied ' + event_msg
|
return "Debug tweak applied " + event_msg
|
||||||
if event_type == EventLogTypes.FAILED_TX_B_REFUND:
|
if event_type == EventLogTypes.FAILED_TX_B_REFUND:
|
||||||
return 'Failed to publish lock tx B refund'
|
return "Failed to publish lock tx B refund"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_INVALID:
|
if event_type == EventLogTypes.LOCK_TX_B_INVALID:
|
||||||
return 'Detected invalid lock Tx B'
|
return "Detected invalid lock Tx B"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
|
||||||
return 'Lock tx A refund tx published'
|
return "Lock tx A refund tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
|
||||||
return 'Lock tx A refund spend tx published'
|
return "Lock tx A refund spend tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED:
|
||||||
return 'Lock tx A refund swipe tx published'
|
return "Lock tx A refund swipe tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED:
|
||||||
return 'Lock tx B refund tx published'
|
return "Lock tx B refund tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED:
|
||||||
return 'Lock tx A spend tx published'
|
return "Lock tx A spend tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED:
|
||||||
return 'Lock tx B spend tx published'
|
return "Lock tx B spend tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN:
|
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN:
|
||||||
return 'Lock tx A refund tx seen in chain'
|
return "Lock tx A refund tx seen in chain"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN:
|
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN:
|
||||||
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:
|
if event_type == EventLogTypes.ERROR:
|
||||||
return 'Error: ' + event_msg
|
return "Error: " + event_msg
|
||||||
if event_type == EventLogTypes.AUTOMATION_CONSTRAINT:
|
if event_type == EventLogTypes.AUTOMATION_CONSTRAINT:
|
||||||
return 'Failed auto accepting'
|
return "Failed auto accepting"
|
||||||
if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID:
|
if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID:
|
||||||
return 'Auto accepting'
|
return "Auto accepting"
|
||||||
if event_type == EventLogTypes.ITX_PUBLISHED:
|
if event_type == EventLogTypes.ITX_PUBLISHED:
|
||||||
return 'Initiate tx published'
|
return "Initiate tx published"
|
||||||
if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED:
|
if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED:
|
||||||
return 'Initiate tx redeem tx published'
|
return "Initiate tx redeem tx published"
|
||||||
if event_type == EventLogTypes.ITX_REFUND_PUBLISHED:
|
if event_type == EventLogTypes.ITX_REFUND_PUBLISHED:
|
||||||
return 'Initiate tx refund tx published'
|
return "Initiate tx refund tx published"
|
||||||
if event_type == EventLogTypes.PTX_PUBLISHED:
|
if event_type == EventLogTypes.PTX_PUBLISHED:
|
||||||
return 'Participate tx published'
|
return "Participate tx published"
|
||||||
if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED:
|
if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED:
|
||||||
return 'Participate tx redeem tx published'
|
return "Participate tx redeem tx published"
|
||||||
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
|
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
|
||||||
return 'Participate tx refund tx published'
|
return "Participate tx refund tx published"
|
||||||
|
if event_type == EventLogTypes.BCH_MERCY_TX_FOUND:
|
||||||
|
return "BCH mercy tx found"
|
||||||
|
if event_type == EventLogTypes.BCH_MERCY_TX_PUBLISHED:
|
||||||
|
return "Lock tx B mercy tx published"
|
||||||
|
|
||||||
|
|
||||||
def getVoutByAddress(txjs, p2sh):
|
def getVoutByAddress(txjs, p2sh):
|
||||||
for o in txjs['vout']:
|
for o in txjs["vout"]:
|
||||||
try:
|
try:
|
||||||
if 'address' in o['scriptPubKey'] and o['scriptPubKey']['address'] == p2sh:
|
if "address" in o["scriptPubKey"] and o["scriptPubKey"]["address"] == p2sh:
|
||||||
return o['n']
|
return o["n"]
|
||||||
if p2sh in o['scriptPubKey']['addresses']:
|
if p2sh in o["scriptPubKey"]["addresses"]:
|
||||||
return o['n']
|
return o["n"]
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise ValueError('Address output not found in txn')
|
raise ValueError("Address output not found in txn")
|
||||||
|
|
||||||
|
|
||||||
def getVoutByScriptPubKey(txjs, scriptPubKey_hex: str) -> int:
|
def getVoutByScriptPubKey(txjs, scriptPubKey_hex: str) -> int:
|
||||||
for o in txjs['vout']:
|
for o in txjs["vout"]:
|
||||||
try:
|
try:
|
||||||
if scriptPubKey_hex == o['scriptPubKey']['hex']:
|
if scriptPubKey_hex == o["scriptPubKey"]["hex"]:
|
||||||
return o['n']
|
return o["n"]
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise ValueError('scriptPubKey output not found in txn')
|
raise ValueError("scriptPubKey output not found in txn")
|
||||||
|
|
||||||
|
|
||||||
def replaceAddrPrefix(addr, coin_type, chain_name, addr_type='pubkey_address'):
|
def replaceAddrPrefix(addr, coin_type, chain_name, addr_type="pubkey_address"):
|
||||||
return encodeAddress(bytes((chainparams[coin_type][chain_name][addr_type],)) + decodeAddress(addr)[1:])
|
return encodeAddress(
|
||||||
|
bytes((chainparams[coin_type][chain_name][addr_type],))
|
||||||
|
+ decodeAddress(addr)[1:]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def getOfferProofOfFundsHash(offer_msg, offer_addr):
|
def getOfferProofOfFundsHash(offer_msg, offer_addr):
|
||||||
# TODO: Hash must not include proof_of_funds sig if it exists in offer_msg
|
# TODO: Hash must not include proof_of_funds sig if it exists in offer_msg
|
||||||
h = hashlib.sha256()
|
h = hashlib.sha256()
|
||||||
h.update(offer_addr.encode('utf-8'))
|
h.update(offer_addr.encode("utf-8"))
|
||||||
offer_bytes = offer_msg.SerializeToString()
|
offer_bytes = offer_msg.to_bytes()
|
||||||
h.update(offer_bytes)
|
h.update(offer_bytes)
|
||||||
return h.digest()
|
return h.digest()
|
||||||
|
|
||||||
@@ -484,33 +510,48 @@ def getLastBidState(packed_states):
|
|||||||
num_states = len(packed_states) // 12
|
num_states = len(packed_states) // 12
|
||||||
if num_states < 2:
|
if num_states < 2:
|
||||||
return BidStates.BID_STATE_UNKNOWN
|
return BidStates.BID_STATE_UNKNOWN
|
||||||
return struct.unpack_from('<i', packed_states[(num_states - 2) * 12:])[0]
|
return struct.unpack_from("<i", packed_states[(num_states - 2) * 12 :])[0]
|
||||||
try:
|
try:
|
||||||
num_states = len(packed_states) // 12
|
num_states = len(packed_states) // 12
|
||||||
if num_states < 2:
|
if num_states < 2:
|
||||||
return BidStates.BID_STATE_UNKNOWN
|
return BidStates.BID_STATE_UNKNOWN
|
||||||
return struct.unpack_from('<i', packed_states[(num_states - 2) * 12:])[0]
|
return struct.unpack_from("<i", packed_states[(num_states - 2) * 12 :])[0]
|
||||||
except Exception:
|
except Exception:
|
||||||
return BidStates.BID_STATE_UNKNOWN
|
return BidStates.BID_STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def strSwapType(swap_type):
|
def strSwapType(swap_type):
|
||||||
if swap_type == SwapTypes.SELLER_FIRST:
|
if swap_type == SwapTypes.SELLER_FIRST:
|
||||||
return 'seller_first'
|
return "seller_first"
|
||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
return 'xmr_swap'
|
return "xmr_swap"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def strSwapDesc(swap_type):
|
def strSwapDesc(swap_type):
|
||||||
if swap_type == SwapTypes.SELLER_FIRST:
|
if swap_type == SwapTypes.SELLER_FIRST:
|
||||||
return 'Secret Hash'
|
return "Secret Hash"
|
||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
return 'Adaptor Sig'
|
return "Adaptor Sig"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
inactive_states = [BidStates.SWAP_COMPLETED, BidStates.BID_ERROR, BidStates.BID_REJECTED, BidStates.SWAP_TIMEDOUT, BidStates.BID_ABANDONED, BidStates.BID_EXPIRED]
|
inactive_states = [
|
||||||
|
BidStates.SWAP_COMPLETED,
|
||||||
|
BidStates.BID_ERROR,
|
||||||
|
BidStates.BID_REJECTED,
|
||||||
|
BidStates.SWAP_TIMEDOUT,
|
||||||
|
BidStates.BID_ABANDONED,
|
||||||
|
BidStates.BID_EXPIRED,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def canAcceptBidState(state):
|
||||||
|
return state in (
|
||||||
|
BidStates.BID_RECEIVED,
|
||||||
|
BidStates.BID_AACCEPT_DELAY,
|
||||||
|
BidStates.BID_AACCEPT_FAIL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def isActiveBidState(state):
|
def isActiveBidState(state):
|
||||||
|
|||||||
1
basicswap/bin/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name = "bin"
|
||||||
2952
basicswap/bin/prepare.py
Executable file
649
basicswap/bin/run.py
Executable file
@@ -0,0 +1,649 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import basicswap.config as cfg
|
||||||
|
from basicswap import __version__
|
||||||
|
from basicswap.ui.util import getCoinName
|
||||||
|
from basicswap.basicswap import BasicSwap
|
||||||
|
from basicswap.chainparams import chainparams, Coins
|
||||||
|
from basicswap.http_server import HttpThread
|
||||||
|
from basicswap.contrib.websocket_server import WebsocketServer
|
||||||
|
|
||||||
|
|
||||||
|
initial_logger = logging.getLogger()
|
||||||
|
initial_logger.level = logging.DEBUG
|
||||||
|
if not len(initial_logger.handlers):
|
||||||
|
initial_logger.addHandler(initial_logger.StreamHandler(sys.stdout))
|
||||||
|
logger = initial_logger
|
||||||
|
|
||||||
|
swap_client = None
|
||||||
|
|
||||||
|
|
||||||
|
class Daemon:
|
||||||
|
__slots__ = ("handle", "files")
|
||||||
|
|
||||||
|
def __init__(self, handle, files):
|
||||||
|
self.handle = handle
|
||||||
|
self.files = files
|
||||||
|
|
||||||
|
|
||||||
|
def is_known_coin(coin_name: str) -> bool:
|
||||||
|
for k, v in chainparams.items():
|
||||||
|
if coin_name == v["name"]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
os.write(
|
||||||
|
sys.stdout.fileno(), f"Signal {sig} detected, ending program.\n".encode("utf-8")
|
||||||
|
)
|
||||||
|
if swap_client is not None and not swap_client.chainstate_delay_event.is_set():
|
||||||
|
swap_client.stopRunning()
|
||||||
|
|
||||||
|
|
||||||
|
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||||
|
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
||||||
|
datadir_path = os.path.expanduser(node_dir)
|
||||||
|
|
||||||
|
# Rewrite litecoin.conf
|
||||||
|
# TODO: Remove
|
||||||
|
needs_rewrite: bool = False
|
||||||
|
ltc_conf_path = os.path.join(datadir_path, "litecoin.conf")
|
||||||
|
if os.path.exists(ltc_conf_path):
|
||||||
|
with open(ltc_conf_path) as fp:
|
||||||
|
for line in fp:
|
||||||
|
line = line.strip()
|
||||||
|
if line.endswith("=onion"):
|
||||||
|
needs_rewrite = True
|
||||||
|
break
|
||||||
|
if needs_rewrite:
|
||||||
|
logger.info("Rewriting litecoin.conf")
|
||||||
|
shutil.copyfile(ltc_conf_path, ltc_conf_path + ".last")
|
||||||
|
with (
|
||||||
|
open(ltc_conf_path + ".last") as fp_from,
|
||||||
|
open(ltc_conf_path, "w") as fp_to,
|
||||||
|
):
|
||||||
|
for line in fp_from:
|
||||||
|
if line.strip().endswith("=onion"):
|
||||||
|
fp_to.write(line.strip()[:-6] + "\n")
|
||||||
|
else:
|
||||||
|
fp_to.write(line)
|
||||||
|
|
||||||
|
args = [
|
||||||
|
daemon_bin,
|
||||||
|
]
|
||||||
|
add_datadir: bool = extra_config.get("add_datadir", True)
|
||||||
|
if add_datadir:
|
||||||
|
args.append("-datadir=" + datadir_path)
|
||||||
|
args += opts
|
||||||
|
logger.info("Starting node {}".format(daemon_bin))
|
||||||
|
logger.debug("Arguments {}".format(" ".join(args)))
|
||||||
|
|
||||||
|
opened_files = []
|
||||||
|
if extra_config.get("stdout_to_file", False):
|
||||||
|
stdout_dest = open(
|
||||||
|
os.path.join(
|
||||||
|
datadir_path, extra_config.get("stdout_filename", "core_stdout.log")
|
||||||
|
),
|
||||||
|
"w",
|
||||||
|
)
|
||||||
|
opened_files.append(stdout_dest)
|
||||||
|
stderr_dest = stdout_dest
|
||||||
|
else:
|
||||||
|
stdout_dest = subprocess.PIPE
|
||||||
|
stderr_dest = subprocess.PIPE
|
||||||
|
|
||||||
|
shell: bool = False
|
||||||
|
if extra_config.get("use_shell", False):
|
||||||
|
args = " ".join(args)
|
||||||
|
shell = True
|
||||||
|
return Daemon(
|
||||||
|
subprocess.Popen(
|
||||||
|
args,
|
||||||
|
shell=shell,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=stdout_dest,
|
||||||
|
stderr=stderr_dest,
|
||||||
|
cwd=datadir_path,
|
||||||
|
),
|
||||||
|
opened_files,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
|
||||||
|
daemon_path = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
||||||
|
|
||||||
|
datadir_path = os.path.expanduser(node_dir)
|
||||||
|
config_filename = (
|
||||||
|
"wownerod.conf" if daemon_bin.startswith("wow") else "monerod.conf"
|
||||||
|
)
|
||||||
|
args = [
|
||||||
|
daemon_path,
|
||||||
|
"--non-interactive",
|
||||||
|
"--config-file=" + os.path.join(datadir_path, config_filename),
|
||||||
|
] + opts
|
||||||
|
logger.info("Starting node {}".format(daemon_bin))
|
||||||
|
logger.debug("Arguments {}".format(" ".join(args)))
|
||||||
|
|
||||||
|
# return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
file_stdout = open(os.path.join(datadir_path, "core_stdout.log"), "w")
|
||||||
|
file_stderr = open(os.path.join(datadir_path, "core_stderr.log"), "w")
|
||||||
|
return Daemon(
|
||||||
|
subprocess.Popen(
|
||||||
|
args,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=file_stdout,
|
||||||
|
stderr=file_stderr,
|
||||||
|
cwd=datadir_path,
|
||||||
|
),
|
||||||
|
[file_stdout, file_stderr],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
|
||||||
|
daemon_path = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
|
||||||
|
args = [daemon_path, "--non-interactive"]
|
||||||
|
|
||||||
|
needs_rewrite: bool = False
|
||||||
|
config_to_remove = [
|
||||||
|
"daemon-address=",
|
||||||
|
"untrusted-daemon=",
|
||||||
|
"trusted-daemon=",
|
||||||
|
"proxy=",
|
||||||
|
]
|
||||||
|
|
||||||
|
data_dir = os.path.expanduser(node_dir)
|
||||||
|
|
||||||
|
wallet_config_filename = (
|
||||||
|
"wownero-wallet-rpc.conf"
|
||||||
|
if wallet_bin.startswith("wow")
|
||||||
|
else "monero_wallet.conf"
|
||||||
|
)
|
||||||
|
config_path = os.path.join(data_dir, wallet_config_filename)
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
args += ["--config-file=" + config_path]
|
||||||
|
with open(config_path) as fp:
|
||||||
|
for line in fp:
|
||||||
|
if any(
|
||||||
|
line.startswith(config_line) for config_line in config_to_remove
|
||||||
|
):
|
||||||
|
logger.warning(
|
||||||
|
"Found old config in monero_wallet.conf: {}".format(
|
||||||
|
line.strip()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
needs_rewrite = True
|
||||||
|
args += opts
|
||||||
|
|
||||||
|
if needs_rewrite:
|
||||||
|
logger.info("Rewriting wallet config")
|
||||||
|
shutil.copyfile(config_path, config_path + ".last")
|
||||||
|
with open(config_path + ".last") as fp_from, open(config_path, "w") as fp_to:
|
||||||
|
for line in fp_from:
|
||||||
|
if not any(
|
||||||
|
line.startswith(config_line) for config_line in config_to_remove
|
||||||
|
):
|
||||||
|
fp_to.write(line)
|
||||||
|
|
||||||
|
logger.info("Starting wallet daemon {}".format(wallet_bin))
|
||||||
|
logger.debug("Arguments {}".format(" ".join(args)))
|
||||||
|
|
||||||
|
# TODO: return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=data_dir)
|
||||||
|
wallet_stdout = open(os.path.join(data_dir, "wallet_stdout.log"), "w")
|
||||||
|
wallet_stderr = open(os.path.join(data_dir, "wallet_stderr.log"), "w")
|
||||||
|
return Daemon(
|
||||||
|
subprocess.Popen(
|
||||||
|
args,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=wallet_stdout,
|
||||||
|
stderr=wallet_stderr,
|
||||||
|
cwd=data_dir,
|
||||||
|
),
|
||||||
|
[wallet_stdout, wallet_stderr],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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 getCoreBinName(coin_id: int, coin_settings, default_name: str) -> str:
|
||||||
|
return coin_settings.get(
|
||||||
|
"core_binname", chainparams[coin_id].get("core_binname", default_name)
|
||||||
|
) + (".exe" if os.name == "nt" else "")
|
||||||
|
|
||||||
|
|
||||||
|
def getWalletBinName(coin_id: int, coin_settings, default_name: str) -> str:
|
||||||
|
return coin_settings.get(
|
||||||
|
"wallet_binname", chainparams[coin_id].get("wallet_binname", default_name)
|
||||||
|
) + (".exe" if os.name == "nt" else "")
|
||||||
|
|
||||||
|
|
||||||
|
def getCoreBinArgs(coin_id: int, coin_settings, prepare=False, use_tor_proxy=False):
|
||||||
|
extra_args = []
|
||||||
|
if "config_filename" in coin_settings:
|
||||||
|
extra_args.append("--conf=" + coin_settings["config_filename"])
|
||||||
|
if "port" in coin_settings and coin_id != Coins.BTC:
|
||||||
|
if prepare is False and use_tor_proxy:
|
||||||
|
if coin_id == Coins.BCH:
|
||||||
|
# Without this BCH (27.1) will bind to the default BTC port, even with proxy set
|
||||||
|
extra_args.append("--bind=127.0.0.1:" + str(int(coin_settings["port"])))
|
||||||
|
else:
|
||||||
|
extra_args.append("--port=" + str(int(coin_settings["port"])))
|
||||||
|
|
||||||
|
# BTC versions from v28 fail to start if the onionport is in use.
|
||||||
|
# As BCH may use port 8334, disable it here.
|
||||||
|
# When tor is enabled a bind option for the onionport will be added to bitcoin.conf.
|
||||||
|
# https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-28.0.md?plain=1#L84
|
||||||
|
if prepare is False and use_tor_proxy is False and coin_id == Coins.BTC:
|
||||||
|
port: int = coin_settings.get("port", 8333)
|
||||||
|
extra_args.append(f"--bind=0.0.0.0:{port}")
|
||||||
|
return extra_args
|
||||||
|
|
||||||
|
|
||||||
|
def runClient(fp, data_dir, chain, start_only_coins):
|
||||||
|
global swap_client, logger
|
||||||
|
daemons = []
|
||||||
|
pids = []
|
||||||
|
threads = []
|
||||||
|
settings_path = os.path.join(data_dir, cfg.CONFIG_FILENAME)
|
||||||
|
pids_path = os.path.join(data_dir, ".pids")
|
||||||
|
|
||||||
|
if os.getenv("WALLET_ENCRYPTION_PWD", "") != "":
|
||||||
|
if "decred" in start_only_coins:
|
||||||
|
# Workaround for dcrwallet requiring password for initial startup
|
||||||
|
logger.warning(
|
||||||
|
"Allowing set WALLET_ENCRYPTION_PWD var with --startonlycoin=decred."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Please unset the WALLET_ENCRYPTION_PWD environment variable."
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(settings_path):
|
||||||
|
raise ValueError("Settings file not found: " + str(settings_path))
|
||||||
|
|
||||||
|
with open(settings_path) as fs:
|
||||||
|
settings = json.load(fs)
|
||||||
|
|
||||||
|
swap_client = BasicSwap(fp, data_dir, settings, chain)
|
||||||
|
logger = swap_client.log
|
||||||
|
|
||||||
|
if os.path.exists(pids_path):
|
||||||
|
with open(pids_path) as fd:
|
||||||
|
for ln in fd:
|
||||||
|
# TODO: try close
|
||||||
|
logger.warning("Found pid for daemon {} ".format(ln.strip()))
|
||||||
|
|
||||||
|
# Ensure daemons are stopped
|
||||||
|
swap_client.stopDaemons()
|
||||||
|
|
||||||
|
# Settings may have been modified
|
||||||
|
settings = swap_client.settings
|
||||||
|
try:
|
||||||
|
# Try start daemons
|
||||||
|
for c, v in settings["chainclients"].items():
|
||||||
|
if len(start_only_coins) > 0 and c not in start_only_coins:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
coin_id = swap_client.getCoinIdFromName(c)
|
||||||
|
display_name = getCoinName(coin_id)
|
||||||
|
except Exception as e: # noqa: F841
|
||||||
|
logger.warning("Not starting unknown coin: {}".format(c))
|
||||||
|
continue
|
||||||
|
if c in ("monero", "wownero"):
|
||||||
|
if v["manage_daemon"] is True:
|
||||||
|
swap_client.log.info(f"Starting {display_name} daemon")
|
||||||
|
filename: str = getCoreBinName(coin_id, v, c + "d")
|
||||||
|
|
||||||
|
daemons.append(startXmrDaemon(v["datadir"], v["bindir"], filename))
|
||||||
|
pid = daemons[-1].handle.pid
|
||||||
|
swap_client.log.info("Started {} {}".format(filename, pid))
|
||||||
|
|
||||||
|
if v["manage_wallet_daemon"] is True:
|
||||||
|
swap_client.log.info(f"Starting {display_name} wallet daemon")
|
||||||
|
daemon_addr = "{}:{}".format(v["rpchost"], v["rpcport"])
|
||||||
|
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
|
||||||
|
coin_id, v["rpchost"]
|
||||||
|
)
|
||||||
|
opts = [
|
||||||
|
"--daemon-address",
|
||||||
|
daemon_addr,
|
||||||
|
]
|
||||||
|
|
||||||
|
proxy_log_str = ""
|
||||||
|
proxy_host, proxy_port = swap_client.getXMRWalletProxy(
|
||||||
|
coin_id, v["rpchost"]
|
||||||
|
)
|
||||||
|
if proxy_host:
|
||||||
|
proxy_log_str = " through proxy"
|
||||||
|
opts += [
|
||||||
|
"--proxy",
|
||||||
|
f"{proxy_host}:{proxy_port}",
|
||||||
|
"--daemon-ssl-allow-any-cert",
|
||||||
|
]
|
||||||
|
|
||||||
|
swap_client.log.info(
|
||||||
|
"daemon-address: {} ({}){}".format(
|
||||||
|
daemon_addr,
|
||||||
|
"trusted" if trusted_daemon else "untrusted",
|
||||||
|
proxy_log_str,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
daemon_rpcuser = v.get("rpcuser", "")
|
||||||
|
daemon_rpcpass = v.get("rpcpassword", "")
|
||||||
|
if daemon_rpcuser != "":
|
||||||
|
opts.append("--daemon-login")
|
||||||
|
opts.append(daemon_rpcuser + ":" + daemon_rpcpass)
|
||||||
|
|
||||||
|
opts.append(
|
||||||
|
"--trusted-daemon" if trusted_daemon else "--untrusted-daemon"
|
||||||
|
)
|
||||||
|
filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc")
|
||||||
|
|
||||||
|
daemons.append(
|
||||||
|
startXmrWalletDaemon(v["datadir"], v["bindir"], filename, opts)
|
||||||
|
)
|
||||||
|
pid = daemons[-1].handle.pid
|
||||||
|
swap_client.log.info("Started {} {}".format(filename, pid))
|
||||||
|
|
||||||
|
continue # /monero
|
||||||
|
|
||||||
|
if c == "decred":
|
||||||
|
appdata = v["datadir"]
|
||||||
|
extra_opts = [
|
||||||
|
f'--appdata="{appdata}"',
|
||||||
|
]
|
||||||
|
use_shell: bool = True if os.name == "nt" else False
|
||||||
|
if v["manage_daemon"] is True:
|
||||||
|
swap_client.log.info(f"Starting {display_name} daemon")
|
||||||
|
filename: str = getCoreBinName(coin_id, v, "dcrd")
|
||||||
|
|
||||||
|
extra_config = {
|
||||||
|
"add_datadir": False,
|
||||||
|
"stdout_to_file": True,
|
||||||
|
"stdout_filename": "dcrd_stdout.log",
|
||||||
|
"use_shell": use_shell,
|
||||||
|
}
|
||||||
|
daemons.append(
|
||||||
|
startDaemon(
|
||||||
|
appdata,
|
||||||
|
v["bindir"],
|
||||||
|
filename,
|
||||||
|
opts=extra_opts,
|
||||||
|
extra_config=extra_config,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pid = daemons[-1].handle.pid
|
||||||
|
swap_client.log.info("Started {} {}".format(filename, pid))
|
||||||
|
|
||||||
|
if v["manage_wallet_daemon"] is True:
|
||||||
|
swap_client.log.info(f"Starting {display_name} wallet daemon")
|
||||||
|
filename: str = getWalletBinName(coin_id, v, "dcrwallet")
|
||||||
|
|
||||||
|
wallet_pwd = v["wallet_pwd"]
|
||||||
|
if wallet_pwd == "":
|
||||||
|
# Only set when in startonlycoin mode
|
||||||
|
wallet_pwd = os.getenv("WALLET_ENCRYPTION_PWD", "")
|
||||||
|
if wallet_pwd != "":
|
||||||
|
extra_opts.append(f'--pass="{wallet_pwd}"')
|
||||||
|
extra_config = {
|
||||||
|
"add_datadir": False,
|
||||||
|
"stdout_to_file": True,
|
||||||
|
"stdout_filename": "dcrwallet_stdout.log",
|
||||||
|
"use_shell": use_shell,
|
||||||
|
}
|
||||||
|
daemons.append(
|
||||||
|
startDaemon(
|
||||||
|
appdata,
|
||||||
|
v["bindir"],
|
||||||
|
filename,
|
||||||
|
opts=extra_opts,
|
||||||
|
extra_config=extra_config,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pid = daemons[-1].handle.pid
|
||||||
|
swap_client.log.info("Started {} {}".format(filename, pid))
|
||||||
|
|
||||||
|
continue # /decred
|
||||||
|
|
||||||
|
if v["manage_daemon"] is True:
|
||||||
|
swap_client.log.info(f"Starting {display_name} daemon")
|
||||||
|
|
||||||
|
filename: str = getCoreBinName(coin_id, v, c + "d")
|
||||||
|
extra_opts = getCoreBinArgs(
|
||||||
|
coin_id, v, use_tor_proxy=swap_client.use_tor_proxy
|
||||||
|
)
|
||||||
|
daemons.append(
|
||||||
|
startDaemon(v["datadir"], v["bindir"], filename, opts=extra_opts)
|
||||||
|
)
|
||||||
|
pid = daemons[-1].handle.pid
|
||||||
|
pids.append((c, pid))
|
||||||
|
swap_client.setDaemonPID(c, pid)
|
||||||
|
swap_client.log.info("Started {} {}".format(filename, pid))
|
||||||
|
if len(pids) > 0:
|
||||||
|
with open(pids_path, "w") as fd:
|
||||||
|
for p in pids:
|
||||||
|
fd.write("{}:{}\n".format(*p))
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
signal.signal(signal.SIGHUP, signal_handler)
|
||||||
|
|
||||||
|
if len(start_only_coins) > 0:
|
||||||
|
logger.info(
|
||||||
|
f"Only running {start_only_coins}. Manually exit with Ctrl + c when ready."
|
||||||
|
)
|
||||||
|
while not swap_client.delay_event.wait(0.5):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
swap_client.start()
|
||||||
|
if "htmlhost" in settings:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
thread_http = HttpThread(
|
||||||
|
fp,
|
||||||
|
settings["htmlhost"],
|
||||||
|
settings["htmlport"],
|
||||||
|
allow_cors,
|
||||||
|
swap_client,
|
||||||
|
)
|
||||||
|
threads.append(thread_http)
|
||||||
|
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.client_port = settings.get(
|
||||||
|
"wsclientport", 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.")
|
||||||
|
while not swap_client.delay_event.wait(0.5):
|
||||||
|
swap_client.update()
|
||||||
|
|
||||||
|
except Exception as e: # noqa: F841
|
||||||
|
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 e: # noqa: F841
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
swap_client.finalise()
|
||||||
|
swap_client.log.info("Stopping HTTP threads.")
|
||||||
|
for t in threads:
|
||||||
|
try:
|
||||||
|
t.stop()
|
||||||
|
t.join()
|
||||||
|
except Exception as e: # noqa: F841
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
closed_pids = []
|
||||||
|
for d in daemons:
|
||||||
|
swap_client.log.info("Interrupting {}".format(d.handle.pid))
|
||||||
|
try:
|
||||||
|
d.handle.send_signal(
|
||||||
|
signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
swap_client.log.info(f"Interrupting {d.handle.pid}, error {e}")
|
||||||
|
for d in daemons:
|
||||||
|
try:
|
||||||
|
d.handle.wait(timeout=120)
|
||||||
|
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
||||||
|
if fp:
|
||||||
|
fp.close()
|
||||||
|
closed_pids.append(d.handle.pid)
|
||||||
|
except Exception as e:
|
||||||
|
swap_client.log.error(f"Error: {e}")
|
||||||
|
|
||||||
|
if os.path.exists(pids_path):
|
||||||
|
with open(pids_path) as fd:
|
||||||
|
lines = fd.read().split("\n")
|
||||||
|
still_running = ""
|
||||||
|
for ln in lines:
|
||||||
|
try:
|
||||||
|
if int(ln.split(":")[1]) not in closed_pids:
|
||||||
|
still_running += ln + "\n"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
with open(pids_path, "w") as fd:
|
||||||
|
fd.write(still_running)
|
||||||
|
|
||||||
|
|
||||||
|
def printVersion():
|
||||||
|
logger.info("Basicswap version: %s", __version__)
|
||||||
|
|
||||||
|
|
||||||
|
def printHelp():
|
||||||
|
print("Usage: basicswap-run ")
|
||||||
|
print("\n--help, -h Print help.")
|
||||||
|
print("--version, -v Print version.")
|
||||||
|
print(
|
||||||
|
"--datadir=PATH Path to basicswap data directory, default:{}.".format(
|
||||||
|
cfg.BASICSWAP_DATADIR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print("--mainnet Run in mainnet mode.")
|
||||||
|
print("--testnet Run in testnet mode.")
|
||||||
|
print("--regtest Run in regtest mode.")
|
||||||
|
print(
|
||||||
|
"--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data_dir = None
|
||||||
|
chain = "mainnet"
|
||||||
|
start_only_coins = set()
|
||||||
|
|
||||||
|
for v in sys.argv[1:]:
|
||||||
|
if len(v) < 2 or v[0] != "-":
|
||||||
|
logger.warning("Unknown argument %s", v)
|
||||||
|
continue
|
||||||
|
|
||||||
|
s = v.split("=")
|
||||||
|
name = s[0].strip()
|
||||||
|
|
||||||
|
for i in range(2):
|
||||||
|
if name[0] == "-":
|
||||||
|
name = name[1:]
|
||||||
|
|
||||||
|
if name == "v" or name == "version":
|
||||||
|
printVersion()
|
||||||
|
return 0
|
||||||
|
if name == "h" or name == "help":
|
||||||
|
printHelp()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if name in ("mainnet", "testnet", "regtest"):
|
||||||
|
chain = name
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(s) == 2:
|
||||||
|
if name == "datadir":
|
||||||
|
data_dir = os.path.expanduser(s[1])
|
||||||
|
continue
|
||||||
|
if name == "startonlycoin":
|
||||||
|
for coin in [s.lower() for s in s[1].split(",")]:
|
||||||
|
if is_known_coin(coin) is False:
|
||||||
|
raise ValueError(f"Unknown coin: {coin}")
|
||||||
|
start_only_coins.add(coin)
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.warning("Unknown argument %s", v)
|
||||||
|
|
||||||
|
if os.name == "nt":
|
||||||
|
logger.warning(
|
||||||
|
"Running on windows is discouraged and windows support may be discontinued in the future. Please consider using the WSL docker setup instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
if data_dir is None:
|
||||||
|
data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
|
||||||
|
logger.info("Using datadir: %s", data_dir)
|
||||||
|
logger.info("Chain: %s", chain)
|
||||||
|
|
||||||
|
if not os.path.exists(data_dir):
|
||||||
|
os.makedirs(data_dir)
|
||||||
|
|
||||||
|
with open(os.path.join(data_dir, "basicswap.log"), "a") as fp:
|
||||||
|
logger.info(
|
||||||
|
os.path.basename(sys.argv[0]) + ", version: " + __version__ + "\n\n"
|
||||||
|
)
|
||||||
|
runClient(fp, data_dir, chain, start_only_coins)
|
||||||
|
|
||||||
|
print("Done.")
|
||||||
|
return swap_client.fail_code if swap_client is not None else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ from .util import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
XMR_COIN = 10**12
|
XMR_COIN = 10**12
|
||||||
|
WOW_COIN = 10**11
|
||||||
|
|
||||||
|
|
||||||
class Coins(IntEnum):
|
class Coins(IntEnum):
|
||||||
@@ -21,399 +23,530 @@ class Coins(IntEnum):
|
|||||||
XMR = 6
|
XMR = 6
|
||||||
PART_BLIND = 7
|
PART_BLIND = 7
|
||||||
PART_ANON = 8
|
PART_ANON = 8
|
||||||
# ZANO = 9
|
WOW = 9
|
||||||
# NDAU = 10
|
# NDAU = 10
|
||||||
PIVX = 11
|
PIVX = 11
|
||||||
DASH = 12
|
DASH = 12
|
||||||
FIRO = 13
|
FIRO = 13
|
||||||
NAV = 14
|
NAV = 14
|
||||||
LTC_MWEB = 15
|
LTC_MWEB = 15
|
||||||
|
# ZANO = 16
|
||||||
|
BCH = 17
|
||||||
|
DOGE = 18
|
||||||
|
|
||||||
|
|
||||||
chainparams = {
|
chainparams = {
|
||||||
Coins.PART: {
|
Coins.PART: {
|
||||||
'name': 'particl',
|
"name": "particl",
|
||||||
'ticker': 'PART',
|
"ticker": "PART",
|
||||||
'message_magic': 'Bitcoin Signed Message:\n',
|
"message_magic": "Bitcoin Signed Message:\n",
|
||||||
'blocks_target': 60 * 2,
|
"blocks_target": 60 * 2,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'mainnet': {
|
"mainnet": {
|
||||||
'rpcport': 51735,
|
"rpcport": 51735,
|
||||||
'pubkey_address': 0x38,
|
"pubkey_address": 0x38,
|
||||||
'script_address': 0x3c,
|
"script_address": 0x3C,
|
||||||
'key_prefix': 0x6c,
|
"key_prefix": 0x6C,
|
||||||
'stealth_key_prefix': 0x14,
|
"stealth_key_prefix": 0x14,
|
||||||
'hrp': 'pw',
|
"hrp": "pw",
|
||||||
'bip44': 44,
|
"bip44": 44,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 51935,
|
"rpcport": 51935,
|
||||||
'pubkey_address': 0x76,
|
"pubkey_address": 0x76,
|
||||||
'script_address': 0x7a,
|
"script_address": 0x7A,
|
||||||
'key_prefix': 0x2e,
|
"key_prefix": 0x2E,
|
||||||
'stealth_key_prefix': 0x15,
|
"stealth_key_prefix": 0x15,
|
||||||
'hrp': 'tpw',
|
"hrp": "tpw",
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 51936,
|
||||||
|
"pubkey_address": 0x76,
|
||||||
|
"script_address": 0x7A,
|
||||||
|
"key_prefix": 0x2E,
|
||||||
|
"stealth_key_prefix": 0x15,
|
||||||
|
"hrp": "rtpw",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 51936,
|
|
||||||
'pubkey_address': 0x76,
|
|
||||||
'script_address': 0x7a,
|
|
||||||
'key_prefix': 0x2e,
|
|
||||||
'stealth_key_prefix': 0x15,
|
|
||||||
'hrp': 'rtpw',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.BTC: {
|
Coins.BTC: {
|
||||||
'name': 'bitcoin',
|
"name": "bitcoin",
|
||||||
'ticker': 'BTC',
|
"ticker": "BTC",
|
||||||
'message_magic': 'Bitcoin Signed Message:\n',
|
"message_magic": "Bitcoin Signed Message:\n",
|
||||||
'blocks_target': 60 * 10,
|
"blocks_target": 60 * 10,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'mainnet': {
|
"mainnet": {
|
||||||
'rpcport': 8332,
|
"rpcport": 8332,
|
||||||
'pubkey_address': 0,
|
"pubkey_address": 0,
|
||||||
'script_address': 5,
|
"script_address": 5,
|
||||||
'key_prefix': 128,
|
"key_prefix": 128,
|
||||||
'hrp': 'bc',
|
"hrp": "bc",
|
||||||
'bip44': 0,
|
"bip44": 0,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
|
"ext_public_key_prefix": 0x0488B21E,
|
||||||
|
"ext_secret_key_prefix": 0x0488ADE4,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 18332,
|
"rpcport": 18332,
|
||||||
'pubkey_address': 111,
|
"pubkey_address": 111,
|
||||||
'script_address': 196,
|
"script_address": 196,
|
||||||
'key_prefix': 239,
|
"key_prefix": 239,
|
||||||
'hrp': 'tb',
|
"hrp": "tb",
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
'name': 'testnet3',
|
"name": "testnet3",
|
||||||
|
"ext_public_key_prefix": 0x043587CF,
|
||||||
|
"ext_secret_key_prefix": 0x04358394,
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 18443,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "bcrt",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
"ext_public_key_prefix": 0x043587CF,
|
||||||
|
"ext_secret_key_prefix": 0x04358394,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 18443,
|
|
||||||
'pubkey_address': 111,
|
|
||||||
'script_address': 196,
|
|
||||||
'key_prefix': 239,
|
|
||||||
'hrp': 'bcrt',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.LTC: {
|
Coins.LTC: {
|
||||||
'name': 'litecoin',
|
"name": "litecoin",
|
||||||
'ticker': 'LTC',
|
"ticker": "LTC",
|
||||||
'message_magic': 'Litecoin Signed Message:\n',
|
"message_magic": "Litecoin Signed Message:\n",
|
||||||
'blocks_target': 60 * 1,
|
"blocks_target": 60 * 1,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'mainnet': {
|
"mainnet": {
|
||||||
'rpcport': 9332,
|
"rpcport": 9332,
|
||||||
'pubkey_address': 48,
|
"pubkey_address": 48,
|
||||||
'script_address': 5,
|
"script_address": 5,
|
||||||
'script_address2': 50,
|
"script_address2": 50,
|
||||||
'key_prefix': 176,
|
"key_prefix": 176,
|
||||||
'hrp': 'ltc',
|
"hrp": "ltc",
|
||||||
'bip44': 2,
|
"bip44": 2,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 19332,
|
"rpcport": 19332,
|
||||||
'pubkey_address': 111,
|
"pubkey_address": 111,
|
||||||
'script_address': 196,
|
"script_address": 196,
|
||||||
'script_address2': 58,
|
"script_address2": 58,
|
||||||
'key_prefix': 239,
|
"key_prefix": 239,
|
||||||
'hrp': 'tltc',
|
"hrp": "tltc",
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
'name': 'testnet4',
|
"name": "testnet4",
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 19443,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"script_address2": 58,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "rltc",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Coins.DOGE: {
|
||||||
|
"name": "dogecoin",
|
||||||
|
"ticker": "DOGE",
|
||||||
|
"message_magic": "Dogecoin Signed Message:\n",
|
||||||
|
"blocks_target": 60 * 1,
|
||||||
|
"decimal_places": 8,
|
||||||
|
"mainnet": {
|
||||||
|
"rpcport": 22555,
|
||||||
|
"pubkey_address": 30,
|
||||||
|
"script_address": 22,
|
||||||
|
"key_prefix": 158,
|
||||||
|
"hrp": "doge",
|
||||||
|
"bip44": 3,
|
||||||
|
"min_amount": 100000, # TODO increase above fee
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
"testnet": {
|
||||||
|
"rpcport": 44555,
|
||||||
|
"pubkey_address": 113,
|
||||||
|
"script_address": 196,
|
||||||
|
"key_prefix": 241,
|
||||||
|
"hrp": "tdge",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
"name": "testnet4",
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 18332,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "rdge",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 19443,
|
|
||||||
'pubkey_address': 111,
|
|
||||||
'script_address': 196,
|
|
||||||
'script_address2': 58,
|
|
||||||
'key_prefix': 239,
|
|
||||||
'hrp': 'rltc',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.DCR: {
|
Coins.DCR: {
|
||||||
'name': 'decred',
|
"name": "decred",
|
||||||
'ticker': 'DCR',
|
"ticker": "DCR",
|
||||||
'message_magic': 'Decred Signed Message:\n',
|
"message_magic": "Decred Signed Message:\n",
|
||||||
'blocks_target': 60 * 5,
|
"blocks_target": 60 * 5,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'mainnet': {
|
"has_multiwallet": False,
|
||||||
'rpcport': 9109,
|
"mainnet": {
|
||||||
'pubkey_address': 0x073f,
|
"rpcport": 9109,
|
||||||
'script_address': 0x071a,
|
"pubkey_address": 0x073F,
|
||||||
'key_prefix': 0x22de,
|
"script_address": 0x071A,
|
||||||
'bip44': 42,
|
"key_prefix": 0x22DE,
|
||||||
'min_amount': 1000,
|
"bip44": 42,
|
||||||
'max_amount': 100000 * COIN,
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 19109,
|
"rpcport": 19109,
|
||||||
'pubkey_address': 0x0f21,
|
"pubkey_address": 0x0F21,
|
||||||
'script_address': 0x0efc,
|
"script_address": 0x0EFC,
|
||||||
'key_prefix': 0x230e,
|
"key_prefix": 0x230E,
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
'name': 'testnet3',
|
"name": "testnet3",
|
||||||
|
},
|
||||||
|
"regtest": { # simnet
|
||||||
|
"rpcport": 18656,
|
||||||
|
"pubkey_address": 0x0E91,
|
||||||
|
"script_address": 0x0E6C,
|
||||||
|
"key_prefix": 0x2307,
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'regtest': { # simnet
|
|
||||||
'rpcport': 18656,
|
|
||||||
'pubkey_address': 0x0e91,
|
|
||||||
'script_address': 0x0e6c,
|
|
||||||
'key_prefix': 0x2307,
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.NMC: {
|
Coins.NMC: {
|
||||||
'name': 'namecoin',
|
"name": "namecoin",
|
||||||
'ticker': 'NMC',
|
"ticker": "NMC",
|
||||||
'message_magic': 'Namecoin Signed Message:\n',
|
"message_magic": "Namecoin Signed Message:\n",
|
||||||
'blocks_target': 60 * 10,
|
"blocks_target": 60 * 10,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'mainnet': {
|
"mainnet": {
|
||||||
'rpcport': 8336,
|
"rpcport": 8336,
|
||||||
'pubkey_address': 52,
|
"pubkey_address": 52,
|
||||||
'script_address': 13,
|
"script_address": 13,
|
||||||
'hrp': 'nc',
|
"hrp": "nc",
|
||||||
'bip44': 7,
|
"bip44": 7,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 18336,
|
"rpcport": 18336,
|
||||||
'pubkey_address': 111,
|
"pubkey_address": 111,
|
||||||
'script_address': 196,
|
"script_address": 196,
|
||||||
'hrp': 'tn',
|
"hrp": "tn",
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
'name': 'testnet3',
|
"name": "testnet3",
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 18443,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"hrp": "ncrt",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 18443,
|
|
||||||
'pubkey_address': 111,
|
|
||||||
'script_address': 196,
|
|
||||||
'hrp': 'ncrt',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.XMR: {
|
Coins.XMR: {
|
||||||
'name': 'monero',
|
"name": "monero",
|
||||||
'ticker': 'XMR',
|
"ticker": "XMR",
|
||||||
'client': 'xmr',
|
"client": "xmr",
|
||||||
'decimal_places': 12,
|
"decimal_places": 12,
|
||||||
'mainnet': {
|
"mainnet": {
|
||||||
'rpcport': 18081,
|
"rpcport": 18081,
|
||||||
'walletrpcport': 18082,
|
"walletrpcport": 18082,
|
||||||
'min_amount': 100000,
|
"min_amount": 1000000000,
|
||||||
'max_amount': 10000 * XMR_COIN,
|
"max_amount": 10000000 * XMR_COIN,
|
||||||
'address_prefix': 18,
|
"address_prefix": 18,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 28081,
|
"rpcport": 28081,
|
||||||
'walletrpcport': 28082,
|
"walletrpcport": 28082,
|
||||||
'min_amount': 100000,
|
"min_amount": 1000000000,
|
||||||
'max_amount': 10000 * XMR_COIN,
|
"max_amount": 10000000 * XMR_COIN,
|
||||||
'address_prefix': 18,
|
"address_prefix": 18,
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 18081,
|
||||||
|
"walletrpcport": 18082,
|
||||||
|
"min_amount": 1000000000,
|
||||||
|
"max_amount": 10000000 * XMR_COIN,
|
||||||
|
"address_prefix": 18,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Coins.WOW: {
|
||||||
|
"name": "wownero",
|
||||||
|
"ticker": "WOW",
|
||||||
|
"client": "wow",
|
||||||
|
"decimal_places": 11,
|
||||||
|
"mainnet": {
|
||||||
|
"rpcport": 34568,
|
||||||
|
"walletrpcport": 34572, # todo
|
||||||
|
"min_amount": 100000000,
|
||||||
|
"max_amount": 10000000 * WOW_COIN,
|
||||||
|
"address_prefix": 4146,
|
||||||
|
},
|
||||||
|
"testnet": {
|
||||||
|
"rpcport": 44568,
|
||||||
|
"walletrpcport": 44572,
|
||||||
|
"min_amount": 100000000,
|
||||||
|
"max_amount": 10000000 * WOW_COIN,
|
||||||
|
"address_prefix": 4146,
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 54568,
|
||||||
|
"walletrpcport": 54572,
|
||||||
|
"min_amount": 100000000,
|
||||||
|
"max_amount": 10000000 * WOW_COIN,
|
||||||
|
"address_prefix": 4146,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 18081,
|
|
||||||
'walletrpcport': 18082,
|
|
||||||
'min_amount': 100000,
|
|
||||||
'max_amount': 10000 * XMR_COIN,
|
|
||||||
'address_prefix': 18,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.PIVX: {
|
Coins.PIVX: {
|
||||||
'name': 'pivx',
|
"name": "pivx",
|
||||||
'ticker': 'PIVX',
|
"ticker": "PIVX",
|
||||||
'message_magic': 'DarkNet Signed Message:\n',
|
"display_name": "PIVX",
|
||||||
'blocks_target': 60 * 1,
|
"message_magic": "DarkNet Signed Message:\n",
|
||||||
'decimal_places': 8,
|
"blocks_target": 60 * 1,
|
||||||
'has_cltv': True,
|
"decimal_places": 8,
|
||||||
'has_csv': False,
|
"has_cltv": True,
|
||||||
'has_segwit': False,
|
"has_csv": False,
|
||||||
'use_ticker_as_name': True,
|
"has_segwit": False,
|
||||||
'mainnet': {
|
"mainnet": {
|
||||||
'rpcport': 51473,
|
"rpcport": 51473,
|
||||||
'pubkey_address': 30,
|
"pubkey_address": 30,
|
||||||
'script_address': 13,
|
"script_address": 13,
|
||||||
'key_prefix': 212,
|
"key_prefix": 212,
|
||||||
'bip44': 119,
|
"bip44": 119,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 51475,
|
"rpcport": 51475,
|
||||||
'pubkey_address': 139,
|
"pubkey_address": 139,
|
||||||
'script_address': 19,
|
"script_address": 19,
|
||||||
'key_prefix': 239,
|
"key_prefix": 239,
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
'name': 'testnet4',
|
"name": "testnet4",
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 51477,
|
||||||
|
"pubkey_address": 139,
|
||||||
|
"script_address": 19,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 51477,
|
|
||||||
'pubkey_address': 139,
|
|
||||||
'script_address': 19,
|
|
||||||
'key_prefix': 239,
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.DASH: {
|
Coins.DASH: {
|
||||||
'name': 'dash',
|
"name": "dash",
|
||||||
'ticker': 'DASH',
|
"ticker": "DASH",
|
||||||
'message_magic': 'DarkCoin Signed Message:\n',
|
"message_magic": "DarkCoin Signed Message:\n",
|
||||||
'blocks_target': 60 * 2.5,
|
"blocks_target": 60 * 2.5,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'has_csv': True,
|
"has_csv": True,
|
||||||
'has_segwit': False,
|
"has_segwit": False,
|
||||||
'mainnet': {
|
"mainnet": {
|
||||||
'rpcport': 9998,
|
"rpcport": 9998,
|
||||||
'pubkey_address': 76,
|
"pubkey_address": 76,
|
||||||
'script_address': 16,
|
"script_address": 16,
|
||||||
'key_prefix': 204,
|
"key_prefix": 204,
|
||||||
'hrp': '',
|
"hrp": "",
|
||||||
'bip44': 5,
|
"bip44": 5,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 19998,
|
"rpcport": 19998,
|
||||||
'pubkey_address': 140,
|
"pubkey_address": 140,
|
||||||
'script_address': 19,
|
"script_address": 19,
|
||||||
'key_prefix': 239,
|
"key_prefix": 239,
|
||||||
'hrp': '',
|
"hrp": "",
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 18332,
|
||||||
|
"pubkey_address": 140,
|
||||||
|
"script_address": 19,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 18332,
|
|
||||||
'pubkey_address': 140,
|
|
||||||
'script_address': 19,
|
|
||||||
'key_prefix': 239,
|
|
||||||
'hrp': '',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.FIRO: {
|
Coins.FIRO: {
|
||||||
'name': 'firo',
|
"name": "firo",
|
||||||
'ticker': 'FIRO',
|
"ticker": "FIRO",
|
||||||
'message_magic': 'Zcoin Signed Message:\n',
|
"message_magic": "Zcoin Signed Message:\n",
|
||||||
'blocks_target': 60 * 10,
|
"blocks_target": 60 * 10,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'has_cltv': False,
|
"has_cltv": False,
|
||||||
'has_csv': False,
|
"has_csv": False,
|
||||||
'has_segwit': False,
|
"has_segwit": False,
|
||||||
'mainnet': {
|
"has_multiwallet": False,
|
||||||
'rpcport': 8888,
|
"mainnet": {
|
||||||
'pubkey_address': 82,
|
"rpcport": 8888,
|
||||||
'script_address': 7,
|
"pubkey_address": 82,
|
||||||
'key_prefix': 210,
|
"script_address": 7,
|
||||||
'hrp': '',
|
"key_prefix": 210,
|
||||||
'bip44': 136,
|
"hrp": "",
|
||||||
'min_amount': 1000,
|
"bip44": 136,
|
||||||
'max_amount': 100000 * COIN,
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
"testnet": {
|
||||||
'rpcport': 18888,
|
"rpcport": 18888,
|
||||||
'pubkey_address': 65,
|
"pubkey_address": 65,
|
||||||
'script_address': 178,
|
"script_address": 178,
|
||||||
'key_prefix': 185,
|
"key_prefix": 185,
|
||||||
'hrp': '',
|
"hrp": "",
|
||||||
'bip44': 1,
|
"bip44": 1,
|
||||||
'min_amount': 1000,
|
"min_amount": 100000,
|
||||||
'max_amount': 100000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 28888,
|
||||||
|
"pubkey_address": 65,
|
||||||
|
"script_address": 178,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 28888,
|
|
||||||
'pubkey_address': 65,
|
|
||||||
'script_address': 178,
|
|
||||||
'key_prefix': 239,
|
|
||||||
'hrp': '',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Coins.NAV: {
|
Coins.NAV: {
|
||||||
'name': 'navcoin',
|
"name": "navcoin",
|
||||||
'ticker': 'NAV',
|
"ticker": "NAV",
|
||||||
'message_magic': 'Navcoin Signed Message:\n',
|
"message_magic": "Navcoin Signed Message:\n",
|
||||||
'blocks_target': 30,
|
"blocks_target": 30,
|
||||||
'decimal_places': 8,
|
"decimal_places": 8,
|
||||||
'has_csv': True,
|
"has_csv": True,
|
||||||
'has_segwit': True,
|
"has_segwit": True,
|
||||||
'mainnet': {
|
"has_multiwallet": False,
|
||||||
'rpcport': 44444,
|
"mainnet": {
|
||||||
'pubkey_address': 53,
|
"rpcport": 44444,
|
||||||
'script_address': 85,
|
"pubkey_address": 53,
|
||||||
'key_prefix': 150,
|
"script_address": 85,
|
||||||
'hrp': '',
|
"key_prefix": 150,
|
||||||
'bip44': 130,
|
"hrp": "",
|
||||||
'min_amount': 1000,
|
"bip44": 130,
|
||||||
'max_amount': 100000 * COIN,
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
"testnet": {
|
||||||
|
"rpcport": 44445,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 44446,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Coins.BCH: {
|
||||||
|
"name": "bitcoincash",
|
||||||
|
"ticker": "BCH",
|
||||||
|
"display_name": "Bitcoin Cash",
|
||||||
|
"message_magic": "Bitcoin Signed Message:\n",
|
||||||
|
"blocks_target": 60 * 2,
|
||||||
|
"decimal_places": 8,
|
||||||
|
"has_cltv": True,
|
||||||
|
"has_csv": True,
|
||||||
|
"has_segwit": False,
|
||||||
|
"cli_binname": "bitcoin-cli",
|
||||||
|
"core_binname": "bitcoind",
|
||||||
|
"mainnet": {
|
||||||
|
"rpcport": 8332,
|
||||||
|
"pubkey_address": 0,
|
||||||
|
"script_address": 5,
|
||||||
|
"key_prefix": 128,
|
||||||
|
"hrp": "bitcoincash",
|
||||||
|
"bip44": 0,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
},
|
||||||
|
"testnet": {
|
||||||
|
"rpcport": 18332,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "bchtest",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
|
"name": "testnet3",
|
||||||
|
},
|
||||||
|
"regtest": {
|
||||||
|
"rpcport": 18443,
|
||||||
|
"pubkey_address": 111,
|
||||||
|
"script_address": 196,
|
||||||
|
"key_prefix": 239,
|
||||||
|
"hrp": "bchreg",
|
||||||
|
"bip44": 1,
|
||||||
|
"min_amount": 100000,
|
||||||
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
'testnet': {
|
|
||||||
'rpcport': 44445,
|
|
||||||
'pubkey_address': 111,
|
|
||||||
'script_address': 196,
|
|
||||||
'key_prefix': 239,
|
|
||||||
'hrp': '',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
},
|
},
|
||||||
'regtest': {
|
|
||||||
'rpcport': 44446,
|
|
||||||
'pubkey_address': 111,
|
|
||||||
'script_address': 196,
|
|
||||||
'key_prefix': 239,
|
|
||||||
'hrp': '',
|
|
||||||
'bip44': 1,
|
|
||||||
'min_amount': 1000,
|
|
||||||
'max_amount': 100000 * COIN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name_map = {}
|
||||||
ticker_map = {}
|
ticker_map = {}
|
||||||
|
|
||||||
|
|
||||||
for c, params in chainparams.items():
|
for c, params in chainparams.items():
|
||||||
ticker_map[params['ticker'].lower()] = c
|
name_map[params["name"].lower()] = c
|
||||||
|
ticker_map[params["ticker"].lower()] = c
|
||||||
|
|
||||||
|
|
||||||
def getCoinIdFromTicker(ticker):
|
def getCoinIdFromTicker(ticker: str) -> str:
|
||||||
try:
|
try:
|
||||||
return ticker_map[ticker.lower()]
|
return ticker_map[ticker.lower()]
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('Unknown coin')
|
raise ValueError(f"Unknown coin {ticker}")
|
||||||
|
|
||||||
|
|
||||||
|
def getCoinIdFromName(name: str) -> str:
|
||||||
|
try:
|
||||||
|
return name_map[name.lower()]
|
||||||
|
except Exception:
|
||||||
|
raise ValueError(f"Unknown coin {name}")
|
||||||
|
|||||||
@@ -1,38 +1,56 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2023 tecnovert
|
# Copyright (c) 2019-2024 The Basicswap developers
|
||||||
# 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"
|
||||||
BASICSWAP_DATADIR = os.getenv('BASICSWAP_DATADIR', '~/.basicswap')
|
BASICSWAP_DATADIR = os.getenv("BASICSWAP_DATADIR", os.path.join("~", ".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', '~/.basicswap/bin'))
|
DEFAULT_TEST_BINDIR = os.path.expanduser(
|
||||||
|
os.getenv("DEFAULT_TEST_BINDIR", os.path.join("~", ".basicswap", "bin"))
|
||||||
|
)
|
||||||
|
|
||||||
bin_suffix = ('.exe' if os.name == 'nt' else '')
|
bin_suffix = ".exe" if os.name == "nt" else ""
|
||||||
PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'particl')))
|
PARTICL_BINDIR = os.path.expanduser(
|
||||||
PARTICLD = os.getenv('PARTICLD', 'particld' + bin_suffix)
|
os.getenv("PARTICL_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "particl"))
|
||||||
PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli' + bin_suffix)
|
)
|
||||||
PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx' + bin_suffix)
|
PARTICLD = os.getenv("PARTICLD", "particld" + bin_suffix)
|
||||||
|
PARTICL_CLI = os.getenv("PARTICL_CLI", "particl-cli" + bin_suffix)
|
||||||
|
PARTICL_TX = os.getenv("PARTICL_TX", "particl-tx" + bin_suffix)
|
||||||
|
|
||||||
BITCOIN_BINDIR = os.path.expanduser(os.getenv('BITCOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'bitcoin')))
|
BITCOIN_BINDIR = os.path.expanduser(
|
||||||
BITCOIND = os.getenv('BITCOIND', 'bitcoind' + bin_suffix)
|
os.getenv("BITCOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "bitcoin"))
|
||||||
BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli' + bin_suffix)
|
)
|
||||||
BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx' + bin_suffix)
|
BITCOIND = os.getenv("BITCOIND", "bitcoind" + bin_suffix)
|
||||||
|
BITCOIN_CLI = os.getenv("BITCOIN_CLI", "bitcoin-cli" + bin_suffix)
|
||||||
|
BITCOIN_TX = os.getenv("BITCOIN_TX", "bitcoin-tx" + bin_suffix)
|
||||||
|
|
||||||
LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'litecoin')))
|
LITECOIN_BINDIR = os.path.expanduser(
|
||||||
LITECOIND = os.getenv('LITECOIND', 'litecoind' + bin_suffix)
|
os.getenv("LITECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "litecoin"))
|
||||||
LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + bin_suffix)
|
)
|
||||||
LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx' + bin_suffix)
|
LITECOIND = os.getenv("LITECOIND", "litecoind" + bin_suffix)
|
||||||
|
LITECOIN_CLI = os.getenv("LITECOIN_CLI", "litecoin-cli" + bin_suffix)
|
||||||
|
LITECOIN_TX = os.getenv("LITECOIN_TX", "litecoin-tx" + bin_suffix)
|
||||||
|
|
||||||
NAMECOIN_BINDIR = os.path.expanduser(os.getenv('NAMECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'namecoin')))
|
DOGECOIND = os.getenv("DOGECOIND", "dogecoind" + bin_suffix)
|
||||||
NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + bin_suffix)
|
DOGECOIN_CLI = os.getenv("DOGECOIN_CLI", "dogecoin-cli" + bin_suffix)
|
||||||
NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix)
|
DOGECOIN_TX = os.getenv("DOGECOIN_TX", "dogecoin-tx" + bin_suffix)
|
||||||
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')))
|
NAMECOIN_BINDIR = os.path.expanduser(
|
||||||
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
|
os.getenv("NAMECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "namecoin"))
|
||||||
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
|
)
|
||||||
|
NAMECOIND = os.getenv("NAMECOIND", "namecoind" + bin_suffix)
|
||||||
|
NAMECOIN_CLI = os.getenv("NAMECOIN_CLI", "namecoin-cli" + bin_suffix)
|
||||||
|
NAMECOIN_TX = os.getenv("NAMECOIN_TX", "namecoin-tx" + bin_suffix)
|
||||||
|
|
||||||
|
XMR_BINDIR = os.path.expanduser(
|
||||||
|
os.getenv("XMR_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "monero"))
|
||||||
|
)
|
||||||
|
XMRD = os.getenv("XMRD", "monerod" + bin_suffix)
|
||||||
|
XMR_WALLET_RPC = os.getenv("XMR_WALLET_RPC", "monero-wallet-rpc" + bin_suffix)
|
||||||
|
|
||||||
|
# NOTE: Adding coin definitions here is deprecated. Please add in coin test file.
|
||||||
|
|||||||
3
basicswap/contrib/mnemonic/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .mnemonic import Mnemonic
|
||||||
|
|
||||||
|
__all__ = ["Mnemonic"]
|
||||||
298
basicswap/contrib/mnemonic/mnemonic.py
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2013 Pavol Rusnak
|
||||||
|
# Copyright (c) 2017 mruddy
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
# this software and associated documentation files (the "Software"), to deal in
|
||||||
|
# the Software without restriction, including without limitation the rights to
|
||||||
|
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
# of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
# so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
import typing as t
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
PBKDF2_ROUNDS = 2048
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Refactored code segments from <https://github.com/keis/base58>
|
||||||
|
def b58encode(v: bytes) -> str:
|
||||||
|
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
p, acc = 1, 0
|
||||||
|
for c in reversed(v):
|
||||||
|
acc += p * c
|
||||||
|
p = p << 8
|
||||||
|
|
||||||
|
string = ""
|
||||||
|
while acc:
|
||||||
|
acc, idx = divmod(acc, 58)
|
||||||
|
string = alphabet[idx : idx + 1] + string
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
class Mnemonic(object):
|
||||||
|
def __init__(self, language: str = "english", wordlist: list[str] | None = None):
|
||||||
|
self.radix = 2048
|
||||||
|
self.language = language
|
||||||
|
|
||||||
|
if wordlist is None:
|
||||||
|
d = os.path.join(os.path.dirname(__file__), f"wordlist/{language}.txt")
|
||||||
|
if os.path.exists(d) and os.path.isfile(d):
|
||||||
|
with open(d, "r", encoding="utf-8") as f:
|
||||||
|
wordlist = [w.strip() for w in f.readlines()]
|
||||||
|
else:
|
||||||
|
raise ConfigurationError("Language not detected")
|
||||||
|
|
||||||
|
if len(wordlist) != self.radix:
|
||||||
|
raise ConfigurationError(f"Wordlist must contain {self.radix} words.")
|
||||||
|
|
||||||
|
self.wordlist = wordlist
|
||||||
|
# Japanese must be joined by ideographic space
|
||||||
|
self.delimiter = "\u3000" if language == "japanese" else " "
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_languages(cls) -> list[str]:
|
||||||
|
return [
|
||||||
|
f.split(".")[0]
|
||||||
|
for f in os.listdir(os.path.join(os.path.dirname(__file__), "wordlist"))
|
||||||
|
if f.endswith(".txt")
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def normalize_string(txt: t.AnyStr) -> str:
|
||||||
|
if isinstance(txt, bytes):
|
||||||
|
utxt = txt.decode("utf8")
|
||||||
|
elif isinstance(txt, str):
|
||||||
|
utxt = txt
|
||||||
|
else:
|
||||||
|
raise TypeError("String value expected")
|
||||||
|
|
||||||
|
return unicodedata.normalize("NFKD", utxt)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def detect_language(cls, code: str) -> str:
|
||||||
|
"""Scan the Mnemonic until the language becomes unambiguous, including as abbreviation prefixes.
|
||||||
|
|
||||||
|
Unfortunately, there are valid words that are ambiguous between languages, which are complete words
|
||||||
|
in one language and are prefixes in another:
|
||||||
|
|
||||||
|
english: abandon ... about
|
||||||
|
french: abandon ... aboutir
|
||||||
|
|
||||||
|
If prefixes remain ambiguous, require exactly one language where word(s) match exactly.
|
||||||
|
"""
|
||||||
|
code = cls.normalize_string(code)
|
||||||
|
possible = set(cls(lang) for lang in cls.list_languages())
|
||||||
|
words = set(code.split())
|
||||||
|
for word in words:
|
||||||
|
# possible languages have candidate(s) starting with the word/prefix
|
||||||
|
possible = set(
|
||||||
|
p for p in possible if any(c.startswith(word) for c in p.wordlist)
|
||||||
|
)
|
||||||
|
if not possible:
|
||||||
|
raise ConfigurationError(f"Language unrecognized for {word!r}")
|
||||||
|
if len(possible) == 1:
|
||||||
|
return possible.pop().language
|
||||||
|
# Multiple languages match: A prefix in many, but an exact match in one determines language.
|
||||||
|
complete = set()
|
||||||
|
for word in words:
|
||||||
|
exact = set(p for p in possible if word in p.wordlist)
|
||||||
|
if len(exact) == 1:
|
||||||
|
complete.update(exact)
|
||||||
|
if len(complete) == 1:
|
||||||
|
return complete.pop().language
|
||||||
|
raise ConfigurationError(
|
||||||
|
f"Language ambiguous between {', '.join(p.language for p in possible)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate(self, strength: int = 128) -> str:
|
||||||
|
"""
|
||||||
|
Create a new mnemonic using a random generated number as entropy.
|
||||||
|
|
||||||
|
As defined in BIP39, the entropy must be a multiple of 32 bits, and its size must be between 128 and 256 bits.
|
||||||
|
Therefore the possible values for `strength` are 128, 160, 192, 224 and 256.
|
||||||
|
|
||||||
|
If not provided, the default entropy length will be set to 128 bits.
|
||||||
|
|
||||||
|
The return is a list of words that encodes the generated entropy.
|
||||||
|
|
||||||
|
:param strength: Number of bytes used as entropy
|
||||||
|
:type strength: int
|
||||||
|
:return: A randomly generated mnemonic
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
if strength not in [128, 160, 192, 224, 256]:
|
||||||
|
raise ValueError(
|
||||||
|
"Invalid strength value. Allowed values are [128, 160, 192, 224, 256]."
|
||||||
|
)
|
||||||
|
return self.to_mnemonic(secrets.token_bytes(strength // 8))
|
||||||
|
|
||||||
|
# Adapted from <http://tinyurl.com/oxmn476>
|
||||||
|
def to_entropy(self, words: list[str] | str) -> bytearray:
|
||||||
|
if not isinstance(words, list):
|
||||||
|
words = words.split(" ")
|
||||||
|
if len(words) not in [12, 15, 18, 21, 24]:
|
||||||
|
raise ValueError(
|
||||||
|
"Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)."
|
||||||
|
% len(words)
|
||||||
|
)
|
||||||
|
# Look up all the words in the list and construct the
|
||||||
|
# concatenation of the original entropy and the checksum.
|
||||||
|
concatLenBits = len(words) * 11
|
||||||
|
concatBits = [False] * concatLenBits
|
||||||
|
wordindex = 0
|
||||||
|
for word in words:
|
||||||
|
# Find the words index in the wordlist
|
||||||
|
ndx = self.wordlist.index(self.normalize_string(word))
|
||||||
|
if ndx < 0:
|
||||||
|
raise LookupError('Unable to find "%s" in word list.' % word)
|
||||||
|
# Set the next 11 bits to the value of the index.
|
||||||
|
for ii in range(11):
|
||||||
|
concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0
|
||||||
|
wordindex += 1
|
||||||
|
checksumLengthBits = concatLenBits // 33
|
||||||
|
entropyLengthBits = concatLenBits - checksumLengthBits
|
||||||
|
# Extract original entropy as bytes.
|
||||||
|
entropy = bytearray(entropyLengthBits // 8)
|
||||||
|
for ii in range(len(entropy)):
|
||||||
|
for jj in range(8):
|
||||||
|
if concatBits[(ii * 8) + jj]:
|
||||||
|
entropy[ii] |= 1 << (7 - jj)
|
||||||
|
# Take the digest of the entropy.
|
||||||
|
hashBytes = hashlib.sha256(entropy).digest()
|
||||||
|
hashBits = list(
|
||||||
|
itertools.chain.from_iterable(
|
||||||
|
[c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Check all the checksum bits.
|
||||||
|
for i in range(checksumLengthBits):
|
||||||
|
if concatBits[entropyLengthBits + i] != hashBits[i]:
|
||||||
|
raise ValueError("Failed checksum.")
|
||||||
|
return entropy
|
||||||
|
|
||||||
|
def to_mnemonic(self, data: bytes) -> str:
|
||||||
|
if len(data) not in [16, 20, 24, 28, 32]:
|
||||||
|
raise ValueError(
|
||||||
|
f"Data length should be one of the following: [16, 20, 24, 28, 32], but it is not {len(data)}."
|
||||||
|
)
|
||||||
|
h = hashlib.sha256(data).hexdigest()
|
||||||
|
b = (
|
||||||
|
bin(int.from_bytes(data, byteorder="big"))[2:].zfill(len(data) * 8)
|
||||||
|
+ bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32]
|
||||||
|
)
|
||||||
|
result = []
|
||||||
|
for i in range(len(b) // 11):
|
||||||
|
idx = int(b[i * 11 : (i + 1) * 11], 2)
|
||||||
|
result.append(self.wordlist[idx])
|
||||||
|
return self.delimiter.join(result)
|
||||||
|
|
||||||
|
def check(self, mnemonic: str) -> bool:
|
||||||
|
mnemonic_list = self.normalize_string(mnemonic).split(" ")
|
||||||
|
# list of valid mnemonic lengths
|
||||||
|
if len(mnemonic_list) not in [12, 15, 18, 21, 24]:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
idx = map(
|
||||||
|
lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic_list
|
||||||
|
)
|
||||||
|
b = "".join(idx)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
l = len(b) # noqa: E741
|
||||||
|
d = b[: l // 33 * 32]
|
||||||
|
h = b[-l // 33 :]
|
||||||
|
nd = int(d, 2).to_bytes(l // 33 * 4, byteorder="big")
|
||||||
|
nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[: l // 33]
|
||||||
|
return h == nh
|
||||||
|
|
||||||
|
def expand_word(self, prefix: str) -> str:
|
||||||
|
if prefix in self.wordlist:
|
||||||
|
return prefix
|
||||||
|
else:
|
||||||
|
matches = [word for word in self.wordlist if word.startswith(prefix)]
|
||||||
|
if len(matches) == 1: # matched exactly one word in the wordlist
|
||||||
|
return matches[0]
|
||||||
|
else:
|
||||||
|
# exact match not found.
|
||||||
|
# this is not a validation routine, just return the input
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
def expand(self, mnemonic: str) -> str:
|
||||||
|
return " ".join(map(self.expand_word, mnemonic.split(" ")))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_seed(cls, mnemonic: str, passphrase: str = "") -> bytes:
|
||||||
|
mnemonic = cls.normalize_string(mnemonic)
|
||||||
|
passphrase = cls.normalize_string(passphrase)
|
||||||
|
passphrase = "mnemonic" + passphrase
|
||||||
|
mnemonic_bytes = mnemonic.encode("utf-8")
|
||||||
|
passphrase_bytes = passphrase.encode("utf-8")
|
||||||
|
stretched = hashlib.pbkdf2_hmac(
|
||||||
|
"sha512", mnemonic_bytes, passphrase_bytes, PBKDF2_ROUNDS
|
||||||
|
)
|
||||||
|
return stretched[:64]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_hd_master_key(seed: bytes, testnet: bool = False) -> str:
|
||||||
|
if len(seed) != 64:
|
||||||
|
raise ValueError("Provided seed should have length of 64")
|
||||||
|
|
||||||
|
# Compute HMAC-SHA512 of seed
|
||||||
|
seed = hmac.new(b"Bitcoin seed", seed, digestmod=hashlib.sha512).digest()
|
||||||
|
|
||||||
|
# Serialization format can be found at: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
|
||||||
|
xprv = b"\x04\x88\xad\xe4" # Version for private mainnet
|
||||||
|
if testnet:
|
||||||
|
xprv = b"\x04\x35\x83\x94" # Version for private testnet
|
||||||
|
xprv += b"\x00" * 9 # Depth, parent fingerprint, and child number
|
||||||
|
xprv += seed[32:] # Chain code
|
||||||
|
xprv += b"\x00" + seed[:32] # Master key
|
||||||
|
|
||||||
|
# Double hash using SHA256
|
||||||
|
hashed_xprv = hashlib.sha256(xprv).digest()
|
||||||
|
hashed_xprv = hashlib.sha256(hashed_xprv).digest()
|
||||||
|
|
||||||
|
# Append 4 bytes of checksum
|
||||||
|
xprv += hashed_xprv[:4]
|
||||||
|
|
||||||
|
# Return base58
|
||||||
|
return b58encode(xprv)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
hex_data = sys.argv[1]
|
||||||
|
else:
|
||||||
|
hex_data = sys.stdin.readline().strip()
|
||||||
|
data = bytes.fromhex(hex_data)
|
||||||
|
m = Mnemonic("english")
|
||||||
|
print(m.to_mnemonic(data))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1
basicswap/contrib/mnemonic/py.typed
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Marker file for PEP 561.
|
||||||
2048
basicswap/contrib/mnemonic/wordlist/chinese_simplified.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/chinese_traditional.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/czech.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/english.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/french.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/italian.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/japanese.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/korean.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/portuguese.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/russian.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/spanish.txt
Normal file
2048
basicswap/contrib/mnemonic/wordlist/turkish.txt
Normal file
64
basicswap/contrib/test_framework/descriptors.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2019 Pieter Wuille
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Utility functions related to output descriptors"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
|
||||||
|
CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||||
|
GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd]
|
||||||
|
|
||||||
|
def descsum_polymod(symbols):
|
||||||
|
"""Internal function that computes the descriptor checksum."""
|
||||||
|
chk = 1
|
||||||
|
for value in symbols:
|
||||||
|
top = chk >> 35
|
||||||
|
chk = (chk & 0x7ffffffff) << 5 ^ value
|
||||||
|
for i in range(5):
|
||||||
|
chk ^= GENERATOR[i] if ((top >> i) & 1) else 0
|
||||||
|
return chk
|
||||||
|
|
||||||
|
def descsum_expand(s):
|
||||||
|
"""Internal function that does the character to symbol expansion"""
|
||||||
|
groups = []
|
||||||
|
symbols = []
|
||||||
|
for c in s:
|
||||||
|
if not c in INPUT_CHARSET:
|
||||||
|
return None
|
||||||
|
v = INPUT_CHARSET.find(c)
|
||||||
|
symbols.append(v & 31)
|
||||||
|
groups.append(v >> 5)
|
||||||
|
if len(groups) == 3:
|
||||||
|
symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2])
|
||||||
|
groups = []
|
||||||
|
if len(groups) == 1:
|
||||||
|
symbols.append(groups[0])
|
||||||
|
elif len(groups) == 2:
|
||||||
|
symbols.append(groups[0] * 3 + groups[1])
|
||||||
|
return symbols
|
||||||
|
|
||||||
|
def descsum_create(s):
|
||||||
|
"""Add a checksum to a descriptor without"""
|
||||||
|
symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
checksum = descsum_polymod(symbols) ^ 1
|
||||||
|
return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8))
|
||||||
|
|
||||||
|
def descsum_check(s, require=True):
|
||||||
|
"""Verify that the checksum is correct in a descriptor"""
|
||||||
|
if not '#' in s:
|
||||||
|
return not require
|
||||||
|
if s[-9] != '#':
|
||||||
|
return False
|
||||||
|
if not all(x in CHECKSUM_CHARSET for x in s[-8:]):
|
||||||
|
return False
|
||||||
|
symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]]
|
||||||
|
return descsum_polymod(symbols) == 1
|
||||||
|
|
||||||
|
def drop_origins(s):
|
||||||
|
'''Drop the key origins from a descriptor'''
|
||||||
|
desc = re.sub(r'\[.+?\]', '', s)
|
||||||
|
if '#' in s:
|
||||||
|
desc = desc[:desc.index('#')]
|
||||||
|
return descsum_create(desc)
|
||||||
1148
basicswap/db.py
@@ -1,27 +1,46 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2024 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# 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 json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from sqlalchemy.orm import scoped_session
|
|
||||||
from .db import (
|
from .db import (
|
||||||
|
AutomationStrategy,
|
||||||
BidState,
|
BidState,
|
||||||
Concepts,
|
Concepts,
|
||||||
AutomationStrategy,
|
CURRENT_DB_DATA_VERSION,
|
||||||
CURRENT_DB_VERSION,
|
CURRENT_DB_VERSION,
|
||||||
CURRENT_DB_DATA_VERSION)
|
)
|
||||||
|
|
||||||
from .basicswap_util import (
|
from .basicswap_util import (
|
||||||
BidStates,
|
BidStates,
|
||||||
strBidState,
|
canAcceptBidState,
|
||||||
isActiveBidState,
|
isActiveBidState,
|
||||||
isErrorBidState,
|
isErrorBidState,
|
||||||
isFailingBidState,
|
isFailingBidState,
|
||||||
isFinalBidState,
|
isFinalBidState,
|
||||||
|
strBidState,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def addBidState(self, state, now, cursor):
|
||||||
|
self.add(
|
||||||
|
BidState(
|
||||||
|
active_ind=1,
|
||||||
|
state_id=int(state),
|
||||||
|
in_progress=isActiveBidState(state),
|
||||||
|
in_error=isErrorBidState(state),
|
||||||
|
swap_failed=isFailingBidState(state),
|
||||||
|
swap_ended=isFinalBidState(state),
|
||||||
|
can_accept=canAcceptBidState(state),
|
||||||
|
label=strBidState(state),
|
||||||
|
created_at=now,
|
||||||
|
),
|
||||||
|
cursor,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,99 +48,128 @@ def upgradeDatabaseData(self, data_version):
|
|||||||
if data_version >= CURRENT_DB_DATA_VERSION:
|
if data_version >= CURRENT_DB_DATA_VERSION:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log.info('Upgrading database records from version %d to %d.', data_version, CURRENT_DB_DATA_VERSION)
|
self.log.info(
|
||||||
with self.mxDB:
|
"Upgrading database records from version %d to %d.",
|
||||||
|
data_version,
|
||||||
|
CURRENT_DB_DATA_VERSION,
|
||||||
|
)
|
||||||
|
cursor = self.openDB()
|
||||||
try:
|
try:
|
||||||
session = scoped_session(self.session_factory)
|
|
||||||
|
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
|
|
||||||
if data_version < 1:
|
if data_version < 1:
|
||||||
session.add(AutomationStrategy(
|
self.add(
|
||||||
|
AutomationStrategy(
|
||||||
active_ind=1,
|
active_ind=1,
|
||||||
label='Accept All',
|
label="Accept All",
|
||||||
type_ind=Concepts.OFFER,
|
type_ind=Concepts.OFFER,
|
||||||
data=json.dumps({'exact_rate_only': True,
|
data=json.dumps(
|
||||||
'max_concurrent_bids': 5}).encode('utf-8'),
|
{"exact_rate_only": True, "max_concurrent_bids": 5}
|
||||||
|
).encode("utf-8"),
|
||||||
only_known_identities=False,
|
only_known_identities=False,
|
||||||
created_at=now))
|
created_at=now,
|
||||||
session.add(AutomationStrategy(
|
),
|
||||||
|
cursor,
|
||||||
|
)
|
||||||
|
self.add(
|
||||||
|
AutomationStrategy(
|
||||||
active_ind=1,
|
active_ind=1,
|
||||||
label='Accept Known',
|
label="Accept Known",
|
||||||
type_ind=Concepts.OFFER,
|
type_ind=Concepts.OFFER,
|
||||||
data=json.dumps({'exact_rate_only': True,
|
data=json.dumps(
|
||||||
'max_concurrent_bids': 5}).encode('utf-8'),
|
{"exact_rate_only": True, "max_concurrent_bids": 5}
|
||||||
|
).encode("utf-8"),
|
||||||
only_known_identities=True,
|
only_known_identities=True,
|
||||||
note='Accept bids from identities with previously successful swaps only',
|
note="Accept bids from identities with previously successful swaps only",
|
||||||
created_at=now))
|
created_at=now,
|
||||||
|
),
|
||||||
|
cursor,
|
||||||
|
)
|
||||||
|
|
||||||
for state in BidStates:
|
for state in BidStates:
|
||||||
session.add(BidState(
|
addBidState(self, state, now, cursor)
|
||||||
active_ind=1,
|
|
||||||
state_id=int(state),
|
|
||||||
in_progress=isActiveBidState(state),
|
|
||||||
in_error=isErrorBidState(state),
|
|
||||||
swap_failed=isFailingBidState(state),
|
|
||||||
swap_ended=isFinalBidState(state),
|
|
||||||
label=strBidState(state),
|
|
||||||
created_at=now))
|
|
||||||
|
|
||||||
if data_version > 0 and data_version < 2:
|
if data_version > 0 and data_version < 2:
|
||||||
for state in (BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS, BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX):
|
for state in (
|
||||||
session.add(BidState(
|
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS,
|
||||||
|
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX,
|
||||||
|
):
|
||||||
|
self.add(
|
||||||
|
BidState(
|
||||||
active_ind=1,
|
active_ind=1,
|
||||||
state_id=int(state),
|
state_id=int(state),
|
||||||
in_progress=isActiveBidState(state),
|
in_progress=isActiveBidState(state),
|
||||||
label=strBidState(state),
|
label=strBidState(state),
|
||||||
created_at=now))
|
created_at=now,
|
||||||
|
),
|
||||||
|
cursor,
|
||||||
|
)
|
||||||
if data_version > 0 and data_version < 3:
|
if data_version > 0 and data_version < 3:
|
||||||
for state in BidStates:
|
for state in BidStates:
|
||||||
in_error = isErrorBidState(state)
|
in_error = isErrorBidState(state)
|
||||||
swap_failed = isFailingBidState(state)
|
swap_failed = isFailingBidState(state)
|
||||||
swap_ended = isFinalBidState(state)
|
swap_ended = isFinalBidState(state)
|
||||||
session.execute('UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id', {'in_error': in_error, 'swap_failed': swap_failed, 'swap_ended': swap_ended, 'state_id': int(state)})
|
cursor.execute(
|
||||||
|
"UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id",
|
||||||
|
{
|
||||||
|
"in_error": in_error,
|
||||||
|
"swap_failed": swap_failed,
|
||||||
|
"swap_ended": swap_ended,
|
||||||
|
"state_id": int(state),
|
||||||
|
},
|
||||||
|
)
|
||||||
if data_version > 0 and data_version < 4:
|
if data_version > 0 and data_version < 4:
|
||||||
for state in (BidStates.BID_REQUEST_SENT, BidStates.BID_REQUEST_ACCEPTED):
|
for state in (
|
||||||
session.add(BidState(
|
BidStates.BID_REQUEST_SENT,
|
||||||
active_ind=1,
|
BidStates.BID_REQUEST_ACCEPTED,
|
||||||
state_id=int(state),
|
):
|
||||||
in_progress=isActiveBidState(state),
|
addBidState(self, state, now, cursor)
|
||||||
in_error=isErrorBidState(state),
|
|
||||||
swap_failed=isFailingBidState(state),
|
if data_version > 0 and data_version < 5:
|
||||||
swap_ended=isFinalBidState(state),
|
for state in (
|
||||||
label=strBidState(state),
|
BidStates.BID_EXPIRED,
|
||||||
created_at=now))
|
BidStates.BID_AACCEPT_DELAY,
|
||||||
|
BidStates.BID_AACCEPT_FAIL,
|
||||||
|
):
|
||||||
|
addBidState(self, state, now, cursor)
|
||||||
|
|
||||||
self.db_data_version = CURRENT_DB_DATA_VERSION
|
self.db_data_version = CURRENT_DB_DATA_VERSION
|
||||||
self.setIntKVInSession('db_data_version', self.db_data_version, session)
|
self.setIntKV("db_data_version", self.db_data_version, cursor)
|
||||||
session.commit()
|
self.commitDB()
|
||||||
self.log.info('Upgraded database records to version {}'.format(self.db_data_version))
|
self.log.info(
|
||||||
|
"Upgraded database records to version {}".format(self.db_data_version)
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
self.closeDB(cursor, commit=False)
|
||||||
session.remove()
|
|
||||||
|
|
||||||
|
|
||||||
def upgradeDatabase(self, db_version):
|
def upgradeDatabase(self, db_version):
|
||||||
if db_version >= CURRENT_DB_VERSION:
|
if db_version >= CURRENT_DB_VERSION:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log.info('Upgrading database from version %d to %d.', db_version, CURRENT_DB_VERSION)
|
self.log.info(
|
||||||
|
f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}."
|
||||||
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
session = scoped_session(self.session_factory)
|
try:
|
||||||
|
cursor = self.openDB()
|
||||||
|
|
||||||
current_version = db_version
|
current_version = db_version
|
||||||
if current_version == 6:
|
if current_version == 6:
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN security_token BLOB')
|
cursor.execute("ALTER TABLE bids ADD COLUMN security_token BLOB")
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN security_token BLOB')
|
cursor.execute("ALTER TABLE offers ADD COLUMN security_token BLOB")
|
||||||
db_version += 1
|
db_version += 1
|
||||||
elif current_version == 7:
|
elif current_version == 7:
|
||||||
session.execute('ALTER TABLE transactions ADD COLUMN block_hash BLOB')
|
cursor.execute("ALTER TABLE transactions ADD COLUMN block_hash BLOB")
|
||||||
session.execute('ALTER TABLE transactions ADD COLUMN block_height INTEGER')
|
cursor.execute(
|
||||||
session.execute('ALTER TABLE transactions ADD COLUMN block_time INTEGER')
|
"ALTER TABLE transactions ADD COLUMN block_height INTEGER"
|
||||||
|
)
|
||||||
|
cursor.execute("ALTER TABLE transactions ADD COLUMN block_time INTEGER")
|
||||||
db_version += 1
|
db_version += 1
|
||||||
elif current_version == 8:
|
elif current_version == 8:
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE wallets (
|
CREATE TABLE wallets (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
coin_id INTEGER,
|
coin_id INTEGER,
|
||||||
@@ -129,30 +177,42 @@ def upgradeDatabase(self, db_version):
|
|||||||
wallet_data VARCHAR,
|
wallet_data VARCHAR,
|
||||||
balance_type INTEGER,
|
balance_type INTEGER,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
|
)
|
||||||
db_version += 1
|
db_version += 1
|
||||||
elif current_version == 9:
|
elif current_version == 9:
|
||||||
session.execute('ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR')
|
cursor.execute("ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR")
|
||||||
db_version += 1
|
db_version += 1
|
||||||
elif current_version == 10:
|
elif current_version == 10:
|
||||||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER')
|
cursor.execute(
|
||||||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER')
|
"ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER"
|
||||||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR')
|
)
|
||||||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR')
|
cursor.execute(
|
||||||
session.execute('UPDATE smsgaddresses SET active_ind = 1, created_at = 1')
|
"ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER"
|
||||||
|
)
|
||||||
|
cursor.execute("ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR")
|
||||||
|
cursor.execute("ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR")
|
||||||
|
cursor.execute(
|
||||||
|
"UPDATE smsgaddresses SET active_ind = 1, created_at = 1"
|
||||||
|
)
|
||||||
|
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN addr_to VARCHAR')
|
cursor.execute("ALTER TABLE offers ADD COLUMN addr_to VARCHAR")
|
||||||
session.execute(f'UPDATE offers SET addr_to = "{self.network_addr}"')
|
cursor.execute(f'UPDATE offers SET addr_to = "{self.network_addr}"')
|
||||||
db_version += 1
|
db_version += 1
|
||||||
elif current_version == 11:
|
elif current_version == 11:
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER')
|
cursor.execute(
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER')
|
"ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER"
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN protocol_version INTEGER')
|
)
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN protocol_version INTEGER')
|
cursor.execute(
|
||||||
session.execute('ALTER TABLE transactions ADD COLUMN tx_data BLOB')
|
"ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER"
|
||||||
|
)
|
||||||
|
cursor.execute("ALTER TABLE bids ADD COLUMN protocol_version INTEGER")
|
||||||
|
cursor.execute("ALTER TABLE offers ADD COLUMN protocol_version INTEGER")
|
||||||
|
cursor.execute("ALTER TABLE transactions ADD COLUMN tx_data BLOB")
|
||||||
db_version += 1
|
db_version += 1
|
||||||
elif current_version == 12:
|
elif current_version == 12:
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE knownidentities (
|
CREATE TABLE knownidentities (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
address VARCHAR,
|
address VARCHAR,
|
||||||
@@ -167,15 +227,19 @@ def upgradeDatabase(self, db_version):
|
|||||||
note VARCHAR,
|
note VARCHAR,
|
||||||
updated_at BIGINT,
|
updated_at BIGINT,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN reject_code INTEGER')
|
)
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN rate INTEGER')
|
cursor.execute("ALTER TABLE bids ADD COLUMN reject_code INTEGER")
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER')
|
cursor.execute("ALTER TABLE bids ADD COLUMN rate INTEGER")
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER')
|
cursor.execute(
|
||||||
|
"ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER"
|
||||||
|
)
|
||||||
|
cursor.execute("ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER")
|
||||||
db_version += 1
|
db_version += 1
|
||||||
elif current_version == 13:
|
elif current_version == 13:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE automationstrategies (
|
CREATE TABLE automationstrategies (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
active_ind INTEGER,
|
active_ind INTEGER,
|
||||||
@@ -187,9 +251,11 @@ def upgradeDatabase(self, db_version):
|
|||||||
|
|
||||||
note VARCHAR,
|
note VARCHAR,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
|
)
|
||||||
|
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE automationlinks (
|
CREATE TABLE automationlinks (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
active_ind INTEGER,
|
active_ind INTEGER,
|
||||||
@@ -204,9 +270,11 @@ def upgradeDatabase(self, db_version):
|
|||||||
|
|
||||||
note VARCHAR,
|
note VARCHAR,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
|
)
|
||||||
|
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE history (
|
CREATE TABLE history (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
concept_type INTEGER,
|
concept_type INTEGER,
|
||||||
@@ -215,9 +283,11 @@ def upgradeDatabase(self, db_version):
|
|||||||
|
|
||||||
note VARCHAR,
|
note VARCHAR,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
|
)
|
||||||
|
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE bidstates (
|
CREATE TABLE bidstates (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
active_ind INTEGER,
|
active_ind INTEGER,
|
||||||
@@ -227,31 +297,47 @@ def upgradeDatabase(self, db_version):
|
|||||||
|
|
||||||
note VARCHAR,
|
note VARCHAR,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
|
)
|
||||||
|
|
||||||
session.execute('ALTER TABLE wallets ADD COLUMN active_ind INTEGER')
|
cursor.execute("ALTER TABLE wallets ADD COLUMN active_ind INTEGER")
|
||||||
session.execute('ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER')
|
cursor.execute(
|
||||||
session.execute('ALTER TABLE eventqueue RENAME TO actions')
|
"ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER"
|
||||||
session.execute('ALTER TABLE actions RENAME COLUMN event_id TO action_id')
|
)
|
||||||
session.execute('ALTER TABLE actions RENAME COLUMN event_type TO action_type')
|
cursor.execute("ALTER TABLE eventqueue RENAME TO actions")
|
||||||
session.execute('ALTER TABLE actions RENAME COLUMN event_data TO action_data')
|
cursor.execute(
|
||||||
|
"ALTER TABLE actions RENAME COLUMN event_id TO action_id"
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"ALTER TABLE actions RENAME COLUMN event_type TO action_type"
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"ALTER TABLE actions RENAME COLUMN event_data TO action_data"
|
||||||
|
)
|
||||||
elif current_version == 14:
|
elif current_version == 14:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB')
|
cursor.execute(
|
||||||
session.execute('ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id')
|
"ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB"
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id"
|
||||||
|
)
|
||||||
elif current_version == 15:
|
elif current_version == 15:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE notifications (
|
CREATE TABLE notifications (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
active_ind INTEGER,
|
active_ind INTEGER,
|
||||||
event_type INTEGER,
|
event_type INTEGER,
|
||||||
event_data BLOB,
|
event_data BLOB,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
|
)
|
||||||
elif current_version == 16:
|
elif current_version == 16:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE prefunded_transactions (
|
CREATE TABLE prefunded_transactions (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
active_ind INTEGER,
|
active_ind INTEGER,
|
||||||
@@ -261,25 +347,31 @@ def upgradeDatabase(self, db_version):
|
|||||||
tx_type INTEGER,
|
tx_type INTEGER,
|
||||||
tx_data BLOB,
|
tx_data BLOB,
|
||||||
used_by BLOB,
|
used_by BLOB,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
|
)
|
||||||
elif current_version == 17:
|
elif current_version == 17:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER')
|
cursor.execute(
|
||||||
session.execute('ALTER TABLE knownidentities ADD COLUMN visibility_override INTEGER')
|
"ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER"
|
||||||
session.execute('ALTER TABLE knownidentities ADD COLUMN data BLOB')
|
)
|
||||||
session.execute('UPDATE knownidentities SET active_ind = 1')
|
cursor.execute(
|
||||||
|
"ALTER TABLE knownidentities ADD COLUMN visibility_override INTEGER"
|
||||||
|
)
|
||||||
|
cursor.execute("ALTER TABLE knownidentities ADD COLUMN data BLOB")
|
||||||
|
cursor.execute("UPDATE knownidentities SET active_ind = 1")
|
||||||
elif current_version == 18:
|
elif current_version == 18:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING')
|
cursor.execute("ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING")
|
||||||
session.execute('ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING')
|
cursor.execute("ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING")
|
||||||
elif current_version == 19:
|
elif current_version == 19:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('ALTER TABLE bidstates ADD COLUMN in_error INTEGER')
|
cursor.execute("ALTER TABLE bidstates ADD COLUMN in_error INTEGER")
|
||||||
session.execute('ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER')
|
cursor.execute("ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER")
|
||||||
session.execute('ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER')
|
cursor.execute("ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER")
|
||||||
elif current_version == 20:
|
elif current_version == 20:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE message_links (
|
CREATE TABLE message_links (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
active_ind INTEGER,
|
active_ind INTEGER,
|
||||||
@@ -291,18 +383,20 @@ def upgradeDatabase(self, db_version):
|
|||||||
msg_type INTEGER,
|
msg_type INTEGER,
|
||||||
msg_sequence INTEGER,
|
msg_sequence INTEGER,
|
||||||
msg_id BLOB,
|
msg_id BLOB,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN bid_reversed INTEGER')
|
)
|
||||||
|
cursor.execute("ALTER TABLE offers ADD COLUMN bid_reversed INTEGER")
|
||||||
elif current_version == 21:
|
elif current_version == 21:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN proof_utxos BLOB')
|
cursor.execute("ALTER TABLE offers ADD COLUMN proof_utxos BLOB")
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN proof_utxos BLOB')
|
cursor.execute("ALTER TABLE bids ADD COLUMN proof_utxos BLOB")
|
||||||
elif current_version == 22:
|
elif current_version == 22:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('ALTER TABLE offers ADD COLUMN amount_to INTEGER')
|
cursor.execute("ALTER TABLE offers ADD COLUMN amount_to INTEGER")
|
||||||
elif current_version == 23:
|
elif current_version == 23:
|
||||||
db_version += 1
|
db_version += 1
|
||||||
session.execute('''
|
cursor.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE checkedblocks (
|
CREATE TABLE checkedblocks (
|
||||||
record_id INTEGER NOT NULL,
|
record_id INTEGER NOT NULL,
|
||||||
created_at BIGINT,
|
created_at BIGINT,
|
||||||
@@ -310,17 +404,24 @@ def upgradeDatabase(self, db_version):
|
|||||||
block_height INTEGER,
|
block_height INTEGER,
|
||||||
block_hash BLOB,
|
block_hash BLOB,
|
||||||
block_time INTEGER,
|
block_time INTEGER,
|
||||||
PRIMARY KEY (record_id))''')
|
PRIMARY KEY (record_id))"""
|
||||||
session.execute('ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB')
|
)
|
||||||
|
cursor.execute("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB")
|
||||||
|
elif current_version == 24:
|
||||||
|
db_version += 1
|
||||||
|
cursor.execute("ALTER TABLE bidstates ADD COLUMN can_accept INTEGER")
|
||||||
if current_version != db_version:
|
if current_version != db_version:
|
||||||
self.db_version = db_version
|
self.db_version = db_version
|
||||||
self.setIntKVInSession('db_version', db_version, session)
|
self.setIntKV("db_version", db_version, cursor)
|
||||||
session.commit()
|
self.commitDB()
|
||||||
session.close()
|
self.log.info("Upgraded database to version {}".format(self.db_version))
|
||||||
session.remove()
|
|
||||||
self.log.info('Upgraded database to version {}'.format(self.db_version))
|
|
||||||
continue
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error("Upgrade failed {}".format(e))
|
||||||
|
self.rollbackDB()
|
||||||
|
finally:
|
||||||
|
self.closeDB(cursor, commit=False)
|
||||||
break
|
break
|
||||||
|
|
||||||
if db_version != CURRENT_DB_VERSION:
|
if db_version != CURRENT_DB_VERSION:
|
||||||
raise ValueError('Unable to upgrade database.')
|
raise ValueError("Unable to upgrade database.")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2023 The BSX Developers
|
# Copyright (c) 2023-2024 The Basicswap Developers
|
||||||
# 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,47 +12,122 @@ from .db import (
|
|||||||
def remove_expired_data(self, time_offset: int = 0):
|
def remove_expired_data(self, time_offset: int = 0):
|
||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
try:
|
try:
|
||||||
session = self.openSession()
|
cursor = self.openDB()
|
||||||
|
|
||||||
active_bids_insert = self.activeBidsQueryStr(now, '', 'b2')
|
active_bids_insert: str = self.activeBidsQueryStr("", "b2")
|
||||||
query_str = f'''
|
query_str = f"""
|
||||||
SELECT o.offer_id FROM offers o
|
SELECT o.offer_id FROM offers o
|
||||||
WHERE o.expire_at <= :expired_at AND 0 = (SELECT COUNT(*) FROM bids b2 WHERE b2.offer_id = o.offer_id AND {active_bids_insert})
|
WHERE o.expire_at <= :expired_at AND 0 = (SELECT COUNT(*) FROM bids b2 WHERE b2.offer_id = o.offer_id AND {active_bids_insert})
|
||||||
'''
|
"""
|
||||||
num_offers = 0
|
num_offers = 0
|
||||||
num_bids = 0
|
num_bids = 0
|
||||||
offer_rows = session.execute(query_str, {'expired_at': now - time_offset})
|
offer_rows = cursor.execute(
|
||||||
|
query_str, {"now": now, "expired_at": now - time_offset}
|
||||||
|
)
|
||||||
for offer_row in offer_rows:
|
for offer_row in offer_rows:
|
||||||
num_offers += 1
|
num_offers += 1
|
||||||
bid_rows = session.execute('SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id', {'offer_id': offer_row[0]})
|
bid_rows = cursor.execute(
|
||||||
|
"SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id",
|
||||||
|
{"offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
for bid_row in bid_rows:
|
for bid_row in bid_rows:
|
||||||
num_bids += 1
|
num_bids += 1
|
||||||
session.execute('DELETE FROM transactions WHERE transactions.bid_id = :bid_id', {'bid_id': bid_row[0]})
|
cursor.execute(
|
||||||
session.execute('DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id', {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]})
|
"DELETE FROM transactions WHERE transactions.bid_id = :bid_id",
|
||||||
session.execute('DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id', {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]})
|
{"bid_id": bid_row[0]},
|
||||||
session.execute('DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id', {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]})
|
)
|
||||||
session.execute('DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id', {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]})
|
cursor.execute(
|
||||||
session.execute('DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id', {'bid_id': bid_row[0]})
|
"DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id",
|
||||||
session.execute('DELETE FROM actions WHERE actions.linked_id = :bid_id', {'bid_id': bid_row[0]})
|
{"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
|
||||||
session.execute('DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id', {'bid_id': bid_row[0]})
|
)
|
||||||
session.execute('DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id', {'bid_id': bid_row[0]})
|
cursor.execute(
|
||||||
session.execute('DELETE FROM bids WHERE bids.bid_id = :bid_id', {'bid_id': bid_row[0]})
|
"DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id",
|
||||||
session.execute('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id', {'type_ind': int(Concepts.BID), 'linked_id': bid_row[0]})
|
{"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id",
|
||||||
|
{"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id",
|
||||||
|
{"type_ind": int(Concepts.BID), "bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id",
|
||||||
|
{"bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM actions WHERE actions.linked_id = :bid_id",
|
||||||
|
{"bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id",
|
||||||
|
{"bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id",
|
||||||
|
{"bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM bids WHERE bids.bid_id = :bid_id",
|
||||||
|
{"bid_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id",
|
||||||
|
{"type_ind": int(Concepts.BID), "linked_id": bid_row[0]},
|
||||||
|
)
|
||||||
|
|
||||||
session.execute('DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
|
cursor.execute(
|
||||||
session.execute('DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
|
"DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id",
|
||||||
session.execute('DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
|
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
|
||||||
session.execute('DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
|
)
|
||||||
session.execute('DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id', {'offer_id': offer_row[0]})
|
cursor.execute(
|
||||||
session.execute('DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id', {'offer_id': offer_row[0]})
|
"DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id",
|
||||||
session.execute('DELETE FROM actions WHERE actions.linked_id = :offer_id', {'offer_id': offer_row[0]})
|
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
|
||||||
session.execute('DELETE FROM offers WHERE offers.offer_id = :offer_id', {'offer_id': offer_row[0]})
|
)
|
||||||
session.execute('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id', {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]})
|
cursor.execute(
|
||||||
|
"DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id",
|
||||||
|
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id",
|
||||||
|
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id",
|
||||||
|
{"offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id",
|
||||||
|
{"offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM actions WHERE actions.linked_id = :offer_id",
|
||||||
|
{"offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM offers WHERE offers.offer_id = :offer_id",
|
||||||
|
{"offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id",
|
||||||
|
{"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]},
|
||||||
|
)
|
||||||
|
|
||||||
if num_offers > 0 or num_bids > 0:
|
if num_offers > 0 or num_bids > 0:
|
||||||
self.log.info('Removed data for {} expired offer{} and {} bid{}.'.format(num_offers, 's' if num_offers != 1 else '', num_bids, 's' if num_bids != 1 else ''))
|
self.log.info(
|
||||||
|
"Removed data for {} expired offer{} and {} bid{}.".format(
|
||||||
|
num_offers,
|
||||||
|
"s" if num_offers != 1 else "",
|
||||||
|
num_bids,
|
||||||
|
"s" if num_bids != 1 else "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
session.execute('DELETE FROM checkedblocks WHERE created_at <= :expired_at', {'expired_at': now - time_offset})
|
cursor.execute(
|
||||||
|
"DELETE FROM checkedblocks WHERE created_at <= :expired_at",
|
||||||
|
{"expired_at": now - time_offset},
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.closeSession(session)
|
self.closeDB(cursor)
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ def encodepoint(P):
|
|||||||
zi = edf.inv(P[2])
|
zi = edf.inv(P[2])
|
||||||
x = (P[0] * zi) % edf.q
|
x = (P[0] * zi) % edf.q
|
||||||
y = (P[1] * zi) % edf.q
|
y = (P[1] * zi) % edf.q
|
||||||
y += ((x & 1) << 255)
|
y += (x & 1) << 255
|
||||||
return y.to_bytes(32, byteorder='little')
|
return y.to_bytes(32, byteorder="little")
|
||||||
|
|
||||||
|
|
||||||
def hashToEd25519(bytes_in):
|
def hashToEd25519(bytes_in):
|
||||||
@@ -22,8 +22,8 @@ def hashToEd25519(bytes_in):
|
|||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
h255 = bytearray(hashed)
|
h255 = bytearray(hashed)
|
||||||
x_sign = 0 if h255[31] & 0x80 == 0 else 1
|
x_sign = 0 if h255[31] & 0x80 == 0 else 1
|
||||||
h255[31] &= 0x7f # Clear top bit
|
h255[31] &= 0x7F # Clear top bit
|
||||||
y = int.from_bytes(h255, byteorder='little')
|
y = int.from_bytes(h255, byteorder="little")
|
||||||
x = edf.xrecover(y, x_sign)
|
x = edf.xrecover(y, x_sign)
|
||||||
if x == 0 and y == 1: # Skip infinity point
|
if x == 0 and y == 1: # Skip infinity point
|
||||||
continue
|
continue
|
||||||
@@ -33,4 +33,4 @@ def hashToEd25519(bytes_in):
|
|||||||
if edf.isoncurve(P) and edf.is_identity(edf.scalarmult(P, edf.l)):
|
if edf.isoncurve(P) and edf.is_identity(edf.scalarmult(P, edf.l)):
|
||||||
return P
|
return P
|
||||||
hashed = hashlib.sha256(hashed).digest()
|
hashed = hashlib.sha256(hashed).digest()
|
||||||
raise ValueError('hashToEd25519 failed')
|
raise ValueError("hashToEd25519 failed")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class Explorer():
|
class Explorer:
|
||||||
def __init__(self, swapclient, coin_type, base_url):
|
def __init__(self, swapclient, coin_type, base_url):
|
||||||
self.swapclient = swapclient
|
self.swapclient = swapclient
|
||||||
self.coin_type = coin_type
|
self.coin_type = coin_type
|
||||||
@@ -15,82 +15,94 @@ class Explorer():
|
|||||||
self.log = self.swapclient.log
|
self.log = self.swapclient.log
|
||||||
|
|
||||||
def readURL(self, url):
|
def readURL(self, url):
|
||||||
self.log.debug('Explorer url: {}'.format(url))
|
self.log.debug("Explorer url: {}".format(url))
|
||||||
return self.swapclient.readURL(url)
|
return self.swapclient.readURL(url)
|
||||||
|
|
||||||
|
|
||||||
class ExplorerInsight(Explorer):
|
class ExplorerInsight(Explorer):
|
||||||
def getChainHeight(self):
|
def getChainHeight(self):
|
||||||
return json.loads(self.readURL(self.base_url + '/sync'))['blockChainHeight']
|
return json.loads(self.readURL(self.base_url + "/sync"))["blockChainHeight"]
|
||||||
|
|
||||||
def getBlock(self, block_hash):
|
def getBlock(self, block_hash):
|
||||||
data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash)))
|
data = json.loads(self.readURL(self.base_url + "/block/{}".format(block_hash)))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def getTransaction(self, txid):
|
def getTransaction(self, txid):
|
||||||
data = json.loads(self.readURL(self.base_url + '/tx/{}'.format(txid)))
|
data = json.loads(self.readURL(self.base_url + "/tx/{}".format(txid)))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def getBalance(self, address):
|
def getBalance(self, address):
|
||||||
data = json.loads(self.readURL(self.base_url + '/addr/{}/balance'.format(address)))
|
data = json.loads(
|
||||||
|
self.readURL(self.base_url + "/addr/{}/balance".format(address))
|
||||||
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def lookupUnspentByAddress(self, address):
|
def lookupUnspentByAddress(self, address):
|
||||||
data = json.loads(self.readURL(self.base_url + '/addr/{}/utxo'.format(address)))
|
data = json.loads(self.readURL(self.base_url + "/addr/{}/utxo".format(address)))
|
||||||
rv = []
|
rv = []
|
||||||
for utxo in data:
|
for utxo in data:
|
||||||
rv.append({
|
rv.append(
|
||||||
'txid': utxo['txid'],
|
{
|
||||||
'index': utxo['vout'],
|
"txid": utxo["txid"],
|
||||||
'height': utxo['height'],
|
"index": utxo["vout"],
|
||||||
'n_conf': utxo['confirmations'],
|
"height": utxo["height"],
|
||||||
'value': utxo['satoshis'],
|
"n_conf": utxo["confirmations"],
|
||||||
})
|
"value": utxo["satoshis"],
|
||||||
|
}
|
||||||
|
)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
class ExplorerBitAps(Explorer):
|
class ExplorerBitAps(Explorer):
|
||||||
def getChainHeight(self):
|
def getChainHeight(self):
|
||||||
return json.loads(self.readURL(self.base_url + '/block/last'))['data']['block']['height']
|
return json.loads(self.readURL(self.base_url + "/block/last"))["data"]["block"][
|
||||||
|
"height"
|
||||||
|
]
|
||||||
|
|
||||||
def getBlock(self, block_hash):
|
def getBlock(self, block_hash):
|
||||||
data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash)))
|
data = json.loads(self.readURL(self.base_url + "/block/{}".format(block_hash)))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def getTransaction(self, txid):
|
def getTransaction(self, txid):
|
||||||
data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(txid)))
|
data = json.loads(self.readURL(self.base_url + "/transaction/{}".format(txid)))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def getBalance(self, address):
|
def getBalance(self, address):
|
||||||
data = json.loads(self.readURL(self.base_url + '/address/state/' + address))
|
data = json.loads(self.readURL(self.base_url + "/address/state/" + address))
|
||||||
return data['data']['balance']
|
return data["data"]["balance"]
|
||||||
|
|
||||||
def lookupUnspentByAddress(self, address):
|
def lookupUnspentByAddress(self, address):
|
||||||
# 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
|
||||||
tx = data['data']['list'][0]
|
tx = data["data"]["list"][0]
|
||||||
tx_data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(tx['txId'])))['data']
|
tx_data = json.loads(
|
||||||
|
self.readURL(self.base_url + "/transaction/{}".format(tx["txId"]))
|
||||||
|
)["data"]
|
||||||
|
|
||||||
for i, vout in tx_data['vOut'].items():
|
for i, vout in tx_data["vOut"].items():
|
||||||
if vout['address'] == address:
|
if vout["address"] == address:
|
||||||
return [{
|
return [
|
||||||
'txid': tx_data['txId'],
|
{
|
||||||
'index': int(i),
|
"txid": tx_data["txId"],
|
||||||
'height': tx_data['blockHeight'],
|
"index": int(i),
|
||||||
'n_conf': tx_data['confirmations'],
|
"height": tx_data["blockHeight"],
|
||||||
'value': vout['value'],
|
"n_conf": tx_data["confirmations"],
|
||||||
}]
|
"value": vout["value"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ExplorerChainz(Explorer):
|
class ExplorerChainz(Explorer):
|
||||||
def getChainHeight(self):
|
def getChainHeight(self):
|
||||||
return int(self.readURL(self.base_url + '?q=getblockcount'))
|
return int(self.readURL(self.base_url + "?q=getblockcount"))
|
||||||
|
|
||||||
def lookupUnspentByAddress(self, address):
|
def lookupUnspentByAddress(self, address):
|
||||||
chain_height = self.getChainHeight()
|
chain_height = self.getChainHeight()
|
||||||
self.log.debug('[rm] chain_height %d', chain_height)
|
self.log.debug("[rm] chain_height %d", chain_height)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -56,12 +57,12 @@ from .ui.page_identity import page_identity
|
|||||||
from .ui.page_smsgaddresses import page_smsgaddresses
|
from .ui.page_smsgaddresses import page_smsgaddresses
|
||||||
from .ui.page_debug import page_debug
|
from .ui.page_debug import page_debug
|
||||||
|
|
||||||
env = Environment(loader=PackageLoader('basicswap', 'templates'))
|
env = Environment(loader=PackageLoader("basicswap", "templates"))
|
||||||
env.filters['formatts'] = format_timestamp
|
env.filters["formatts"] = format_timestamp
|
||||||
|
|
||||||
|
|
||||||
def extractDomain(url):
|
def extractDomain(url):
|
||||||
return url.split('://', 1)[1].split('/', 1)[0]
|
return url.split("://", 1)[1].split("/", 1)[0]
|
||||||
|
|
||||||
|
|
||||||
def listAvailableExplorers(swap_client):
|
def listAvailableExplorers(swap_client):
|
||||||
@@ -69,40 +70,47 @@ def listAvailableExplorers(swap_client):
|
|||||||
for c in Coins:
|
for c in Coins:
|
||||||
if c not in chainparams:
|
if c not in chainparams:
|
||||||
continue
|
continue
|
||||||
for i, e in enumerate(swap_client.coin_clients[c]['explorers']):
|
for i, e in enumerate(swap_client.coin_clients[c]["explorers"]):
|
||||||
explorers.append(('{}_{}'.format(int(c), i), getCoinName(c) + ' - ' + extractDomain(e.base_url)))
|
explorers.append(
|
||||||
|
(
|
||||||
|
"{}_{}".format(int(c), i),
|
||||||
|
getCoinName(c) + " - " + extractDomain(e.base_url),
|
||||||
|
)
|
||||||
|
)
|
||||||
return explorers
|
return explorers
|
||||||
|
|
||||||
|
|
||||||
def listExplorerActions(swap_client):
|
def listExplorerActions(swap_client):
|
||||||
actions = [('height', 'Chain Height'),
|
actions = [
|
||||||
('block', 'Get Block'),
|
("height", "Chain Height"),
|
||||||
('tx', 'Get Transaction'),
|
("block", "Get Block"),
|
||||||
('balance', 'Address Balance'),
|
("tx", "Get Transaction"),
|
||||||
('unspent', 'List Unspent')]
|
("balance", "Address Balance"),
|
||||||
|
("unspent", "List Unspent"),
|
||||||
|
]
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
|
|
||||||
def parse_cmd(cmd: str, type_map: str):
|
def parse_cmd(cmd: str, type_map: str):
|
||||||
params = shlex.split(cmd)
|
params = shlex.split(cmd)
|
||||||
if len(params) < 1:
|
if len(params) < 1:
|
||||||
return '', []
|
return "", []
|
||||||
method = params[0]
|
method = params[0]
|
||||||
typed_params = []
|
typed_params = []
|
||||||
params = params[1:]
|
params = params[1:]
|
||||||
|
|
||||||
for i, param in enumerate(params):
|
for i, param in enumerate(params):
|
||||||
if i >= len(type_map):
|
if i >= len(type_map):
|
||||||
type_ind = 's'
|
type_ind = "s"
|
||||||
else:
|
else:
|
||||||
type_ind = type_map[i]
|
type_ind = type_map[i]
|
||||||
if type_ind == 'i':
|
if type_ind == "i":
|
||||||
typed_params.append(int(param))
|
typed_params.append(int(param))
|
||||||
elif type_ind == 'f':
|
elif type_ind == "f":
|
||||||
typed_params.append(float(param))
|
typed_params.append(float(param))
|
||||||
elif type_ind == 'b':
|
elif type_ind == "b":
|
||||||
typed_params.append(toBool(param))
|
typed_params.append(toBool(param))
|
||||||
elif type_ind == 'j':
|
elif type_ind == "j":
|
||||||
typed_params.append(json.loads(param))
|
typed_params.append(json.loads(param))
|
||||||
else:
|
else:
|
||||||
typed_params.append(param)
|
typed_params.append(param)
|
||||||
@@ -111,7 +119,6 @@ def parse_cmd(cmd: str, type_map: str):
|
|||||||
|
|
||||||
|
|
||||||
class HttpHandler(BaseHTTPRequestHandler):
|
class HttpHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
def log_error(self, format, *args):
|
def log_error(self, format, *args):
|
||||||
super().log_message(format, *args)
|
super().log_message(format, *args)
|
||||||
|
|
||||||
@@ -123,96 +130,112 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
return os.urandom(8).hex()
|
return os.urandom(8).hex()
|
||||||
|
|
||||||
def checkForm(self, post_string, name, messages):
|
def checkForm(self, post_string, name, messages):
|
||||||
if post_string == '':
|
if post_string == "":
|
||||||
return None
|
return None
|
||||||
form_data = parse.parse_qs(post_string)
|
form_data = parse.parse_qs(post_string)
|
||||||
form_id = form_data[b'formid'][0].decode('utf-8')
|
form_id = form_data[b"formid"][0].decode("utf-8")
|
||||||
if self.server.last_form_id.get(name, None) == form_id:
|
if self.server.last_form_id.get(name, None) == form_id:
|
||||||
messages.append('Prevented double submit for form {}.'.format(form_id))
|
messages.append("Prevented double submit for form {}.".format(form_id))
|
||||||
return None
|
return None
|
||||||
self.server.last_form_id[name] = form_id
|
self.server.last_form_id[name] = form_id
|
||||||
return form_data
|
return form_data
|
||||||
|
|
||||||
def render_template(self, template, args_dict, status_code=200, version=__version__):
|
def render_template(
|
||||||
|
self, template, args_dict, status_code=200, version=__version__
|
||||||
|
):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
if swap_client.ws_server:
|
if swap_client.ws_server:
|
||||||
args_dict['ws_url'] = swap_client.ws_server.url
|
args_dict["ws_port"] = swap_client.ws_server.client_port
|
||||||
if swap_client.debug:
|
if swap_client.debug:
|
||||||
args_dict['debug_mode'] = True
|
args_dict["debug_mode"] = True
|
||||||
if swap_client.debug_ui:
|
if swap_client.debug_ui:
|
||||||
args_dict['debug_ui_mode'] = True
|
args_dict["debug_ui_mode"] = True
|
||||||
if swap_client.use_tor_proxy:
|
if swap_client.use_tor_proxy:
|
||||||
args_dict['use_tor_proxy'] = True
|
args_dict["use_tor_proxy"] = True
|
||||||
# TODO: Cache value?
|
# TODO: Cache value?
|
||||||
try:
|
try:
|
||||||
args_dict['tor_established'] = True if get_tor_established_state(swap_client) == '1' else False
|
tor_state = get_tor_established_state(swap_client)
|
||||||
except Exception:
|
args_dict["tor_established"] = True if tor_state == "1" else False
|
||||||
|
except Exception as e:
|
||||||
|
args_dict["tor_established"] = False
|
||||||
if swap_client.debug:
|
if swap_client.debug:
|
||||||
|
swap_client.log.error(f"Error getting Tor state: {str(e)}")
|
||||||
swap_client.log.error(traceback.format_exc())
|
swap_client.log.error(traceback.format_exc())
|
||||||
|
|
||||||
if swap_client._show_notifications:
|
if swap_client._show_notifications:
|
||||||
args_dict['notifications'] = swap_client.getNotifications()
|
args_dict["notifications"] = swap_client.getNotifications()
|
||||||
|
|
||||||
if 'messages' in args_dict:
|
if "messages" in args_dict:
|
||||||
messages_with_ids = []
|
messages_with_ids = []
|
||||||
for msg in args_dict['messages']:
|
for msg in args_dict["messages"]:
|
||||||
messages_with_ids.append((self.server.msg_id_counter, msg))
|
messages_with_ids.append((self.server.msg_id_counter, msg))
|
||||||
self.server.msg_id_counter += 1
|
self.server.msg_id_counter += 1
|
||||||
args_dict['messages'] = messages_with_ids
|
args_dict["messages"] = messages_with_ids
|
||||||
if 'err_messages' in args_dict:
|
if "err_messages" in args_dict:
|
||||||
err_messages_with_ids = []
|
err_messages_with_ids = []
|
||||||
for msg in args_dict['err_messages']:
|
for msg in args_dict["err_messages"]:
|
||||||
err_messages_with_ids.append((self.server.msg_id_counter, msg))
|
err_messages_with_ids.append((self.server.msg_id_counter, msg))
|
||||||
self.server.msg_id_counter += 1
|
self.server.msg_id_counter += 1
|
||||||
args_dict['err_messages'] = err_messages_with_ids
|
args_dict["err_messages"] = err_messages_with_ids
|
||||||
|
|
||||||
shutdown_token = os.urandom(8).hex()
|
shutdown_token = os.urandom(8).hex()
|
||||||
self.server.session_tokens['shutdown'] = shutdown_token
|
self.server.session_tokens["shutdown"] = shutdown_token
|
||||||
args_dict['shutdown_token'] = shutdown_token
|
args_dict["shutdown_token"] = shutdown_token
|
||||||
|
|
||||||
encrypted, locked = swap_client.getLockedState()
|
encrypted, locked = swap_client.getLockedState()
|
||||||
args_dict['encrypted'] = encrypted
|
args_dict["encrypted"] = encrypted
|
||||||
args_dict['locked'] = locked
|
args_dict["locked"] = locked
|
||||||
|
|
||||||
if self.server.msg_id_counter >= 0x7FFFFFFF:
|
if self.server.msg_id_counter >= 0x7FFFFFFF:
|
||||||
self.server.msg_id_counter = 0
|
self.server.msg_id_counter = 0
|
||||||
|
|
||||||
args_dict['version'] = version
|
args_dict["version"] = version
|
||||||
|
|
||||||
self.putHeaders(status_code, 'text/html')
|
self.putHeaders(status_code, "text/html")
|
||||||
return bytes(template.render(
|
return bytes(
|
||||||
|
template.render(
|
||||||
title=self.server.title,
|
title=self.server.title,
|
||||||
h2=self.server.title,
|
h2=self.server.title,
|
||||||
form_id=self.generate_form_id(),
|
form_id=self.generate_form_id(),
|
||||||
**args_dict,
|
**args_dict,
|
||||||
), 'UTF-8')
|
),
|
||||||
|
"UTF-8",
|
||||||
|
)
|
||||||
|
|
||||||
def render_simple_template(self, template, args_dict):
|
def render_simple_template(self, template, args_dict):
|
||||||
swap_client = self.server.swap_client
|
return bytes(
|
||||||
return bytes(template.render(
|
template.render(
|
||||||
title=self.server.title,
|
title=self.server.title,
|
||||||
**args_dict,
|
**args_dict,
|
||||||
), 'UTF-8')
|
),
|
||||||
|
"UTF-8",
|
||||||
|
)
|
||||||
|
|
||||||
def page_info(self, info_str, post_string=None):
|
def page_info(self, info_str, post_string=None):
|
||||||
template = env.get_template('info.html')
|
template = env.get_template("info.html")
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
summary = swap_client.getSummary()
|
summary = swap_client.getSummary()
|
||||||
return self.render_template(template, {
|
return self.render_template(
|
||||||
'title_str': 'BasicSwap Info',
|
template,
|
||||||
'message_str': info_str,
|
{
|
||||||
'summary': summary,
|
"title_str": "BasicSwap Info",
|
||||||
})
|
"message_str": info_str,
|
||||||
|
"summary": summary,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def page_error(self, error_str, post_string=None):
|
def page_error(self, error_str, post_string=None):
|
||||||
template = env.get_template('error.html')
|
template = env.get_template("error.html")
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
summary = swap_client.getSummary()
|
summary = swap_client.getSummary()
|
||||||
return self.render_template(template, {
|
return self.render_template(
|
||||||
'title_str': 'BasicSwap Error',
|
template,
|
||||||
'message_str': error_str,
|
{
|
||||||
'summary': summary,
|
"title_str": "BasicSwap Error",
|
||||||
})
|
"message_str": error_str,
|
||||||
|
"summary": summary,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def page_explorers(self, url_split, post_string):
|
def page_explorers(self, url_split, post_string):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
@@ -224,42 +247,49 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
action = -1
|
action = -1
|
||||||
messages = []
|
messages = []
|
||||||
err_messages = []
|
err_messages = []
|
||||||
form_data = self.checkForm(post_string, 'explorers', err_messages)
|
form_data = self.checkForm(post_string, "explorers", err_messages)
|
||||||
if form_data:
|
if form_data:
|
||||||
|
|
||||||
explorer = form_data[b'explorer'][0].decode('utf-8')
|
explorer = form_data[b"explorer"][0].decode("utf-8")
|
||||||
action = form_data[b'action'][0].decode('utf-8')
|
action = form_data[b"action"][0].decode("utf-8")
|
||||||
|
|
||||||
args = '' if b'args' not in form_data else form_data[b'args'][0].decode('utf-8')
|
args = (
|
||||||
|
""
|
||||||
|
if b"args" not in form_data
|
||||||
|
else form_data[b"args"][0].decode("utf-8")
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
c, e = explorer.split('_')
|
c, e = explorer.split("_")
|
||||||
exp = swap_client.coin_clients[Coins(int(c))]['explorers'][int(e)]
|
exp = swap_client.coin_clients[Coins(int(c))]["explorers"][int(e)]
|
||||||
if action == 'height':
|
if action == "height":
|
||||||
result = str(exp.getChainHeight())
|
result = str(exp.getChainHeight())
|
||||||
elif action == 'block':
|
elif action == "block":
|
||||||
result = dumpj(exp.getBlock(args))
|
result = dumpj(exp.getBlock(args))
|
||||||
elif action == 'tx':
|
elif action == "tx":
|
||||||
result = dumpj(exp.getTransaction(args))
|
result = dumpj(exp.getTransaction(args))
|
||||||
elif action == 'balance':
|
elif action == "balance":
|
||||||
result = dumpj(exp.getBalance(args))
|
result = dumpj(exp.getBalance(args))
|
||||||
elif action == 'unspent':
|
elif action == "unspent":
|
||||||
result = dumpj(exp.lookupUnspentByAddress(args))
|
result = dumpj(exp.lookupUnspentByAddress(args))
|
||||||
else:
|
else:
|
||||||
result = 'Unknown action'
|
result = "Unknown action"
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
result = str(ex)
|
result = str(ex)
|
||||||
|
|
||||||
template = env.get_template('explorers.html')
|
template = env.get_template("explorers.html")
|
||||||
return self.render_template(template, {
|
return self.render_template(
|
||||||
'messages': messages,
|
template,
|
||||||
'err_messages': err_messages,
|
{
|
||||||
'explorers': listAvailableExplorers(swap_client),
|
"messages": messages,
|
||||||
'explorer': explorer,
|
"err_messages": err_messages,
|
||||||
'actions': listExplorerActions(swap_client),
|
"explorers": listAvailableExplorers(swap_client),
|
||||||
'action': action,
|
"explorer": explorer,
|
||||||
'result': result,
|
"actions": listExplorerActions(swap_client),
|
||||||
'summary': summary,
|
"action": action,
|
||||||
})
|
"result": result,
|
||||||
|
"summary": summary,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def page_rpc(self, url_split, post_string):
|
def page_rpc(self, url_split, post_string):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
@@ -267,61 +297,56 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
summary = swap_client.getSummary()
|
summary = swap_client.getSummary()
|
||||||
|
|
||||||
result = None
|
result = None
|
||||||
cmd = ''
|
cmd = ""
|
||||||
|
coin_type_selected = -1
|
||||||
coin_type = -1
|
coin_type = -1
|
||||||
coin_id = -1
|
call_type = "cli"
|
||||||
call_type = 'cli'
|
type_map = ""
|
||||||
type_map = ''
|
|
||||||
messages = []
|
messages = []
|
||||||
err_messages = []
|
err_messages = []
|
||||||
form_data = self.checkForm(post_string, 'rpc', err_messages)
|
form_data = self.checkForm(post_string, "rpc", err_messages)
|
||||||
if form_data:
|
if form_data:
|
||||||
try:
|
try:
|
||||||
call_type = get_data_entry_or(form_data, 'call_type', 'cli')
|
call_type = get_data_entry_or(form_data, "call_type", "cli")
|
||||||
type_map = get_data_entry_or(form_data, 'type_map', '')
|
type_map = get_data_entry_or(form_data, "type_map", "")
|
||||||
try:
|
try:
|
||||||
coin_id = int(get_data_entry(form_data, 'coin_type'))
|
coin_type_selected = get_data_entry(form_data, "coin_type")
|
||||||
if coin_id in (-2, -3, -4):
|
coin_type_split = coin_type_selected.split(",")
|
||||||
coin_type = Coins(Coins.XMR)
|
coin_type = Coins(int(coin_type_split[0]))
|
||||||
elif coin_id in (-5,):
|
coin_variant = int(coin_type_split[1])
|
||||||
coin_type = Coins(Coins.LTC)
|
|
||||||
elif coin_id in (-6,):
|
|
||||||
coin_type = Coins(Coins.DCR)
|
|
||||||
else:
|
|
||||||
coin_type = Coins(coin_id)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('Unknown Coin Type')
|
raise ValueError("Unknown Coin Type")
|
||||||
|
|
||||||
if coin_type in (Coins.DCR,):
|
if coin_type in (Coins.DCR,):
|
||||||
call_type = 'http'
|
call_type = "http"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmd = get_data_entry(form_data, 'cmd')
|
cmd = get_data_entry(form_data, "cmd")
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('Invalid command')
|
raise ValueError("Invalid command")
|
||||||
if coin_type == Coins.XMR:
|
if coin_type in (Coins.XMR, Coins.WOW):
|
||||||
ci = swap_client.ci(coin_type)
|
ci = swap_client.ci(coin_type)
|
||||||
arr = cmd.split(None, 1)
|
arr = cmd.split(None, 1)
|
||||||
method = arr[0]
|
method = arr[0]
|
||||||
params = json.loads(arr[1]) if len(arr) > 1 else []
|
params = json.loads(arr[1]) if len(arr) > 1 else []
|
||||||
if coin_id == -4:
|
if coin_variant == 2:
|
||||||
rv = ci.rpc_wallet(method, params)
|
rv = ci.rpc_wallet(method, params)
|
||||||
elif coin_id == -3:
|
elif coin_variant == 0:
|
||||||
rv = ci.rpc(method, params)
|
rv = ci.rpc(method, params)
|
||||||
elif coin_id == -2:
|
elif coin_variant == 1:
|
||||||
if params == []:
|
if params == []:
|
||||||
params = None
|
params = None
|
||||||
rv = ci.rpc2(method, params)
|
rv = ci.rpc2(method, params)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown XMR RPC variant')
|
raise ValueError("Unknown RPC variant")
|
||||||
result = json.dumps(rv, indent=4)
|
result = json.dumps(rv, indent=4)
|
||||||
else:
|
else:
|
||||||
if call_type == 'http':
|
if call_type == "http":
|
||||||
ci = swap_client.ci(coin_type)
|
ci = swap_client.ci(coin_type)
|
||||||
method, params = parse_cmd(cmd, type_map)
|
method, params = parse_cmd(cmd, type_map)
|
||||||
if coin_id == -6:
|
if coin_variant == 1:
|
||||||
rv = ci.rpc_wallet(method, params)
|
rv = ci.rpc_wallet(method, params)
|
||||||
elif coin_id == -5:
|
elif coin_variant == 2:
|
||||||
rv = ci.rpc_wallet_mweb(method, params)
|
rv = ci.rpc_wallet_mweb(method, params)
|
||||||
else:
|
else:
|
||||||
if coin_type in (Coins.DCR,):
|
if coin_type in (Coins.DCR,):
|
||||||
@@ -330,39 +355,50 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
rv = ci.rpc_wallet(method, params)
|
rv = ci.rpc_wallet(method, params)
|
||||||
if not isinstance(rv, str):
|
if not isinstance(rv, str):
|
||||||
rv = json.dumps(rv, indent=4)
|
rv = json.dumps(rv, indent=4)
|
||||||
result = cmd + '\n' + rv
|
result = cmd + "\n" + rv
|
||||||
else:
|
else:
|
||||||
result = cmd + '\n' + swap_client.callcoincli(coin_type, cmd)
|
result = cmd + "\n" + swap_client.callcoincli(coin_type, cmd)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
result = cmd + '\n' + str(ex)
|
result = cmd + "\n" + str(ex)
|
||||||
if self.server.swap_client.debug is True:
|
if self.server.swap_client.debug is True:
|
||||||
self.server.swap_client.log.error(traceback.format_exc())
|
self.server.swap_client.log.error(traceback.format_exc())
|
||||||
|
|
||||||
template = env.get_template('rpc.html')
|
template = env.get_template("rpc.html")
|
||||||
|
|
||||||
coins = listAvailableCoins(swap_client, with_variants=False)
|
coin_available = listAvailableCoins(swap_client, with_variants=False)
|
||||||
with_xmr: bool = any(c[0] == Coins.XMR for c in coins)
|
with_xmr: bool = any(c[0] == Coins.XMR for c in coin_available)
|
||||||
coins = [c for c in coins if c[0] != Coins.XMR]
|
with_wow: bool = any(c[0] == Coins.WOW for c in coin_available)
|
||||||
|
coins = [
|
||||||
|
(str(c[0]) + ",0", c[1])
|
||||||
|
for c in coin_available
|
||||||
|
if c[0] not in (Coins.XMR, Coins.WOW)
|
||||||
|
]
|
||||||
|
|
||||||
if any(c[0] == Coins.DCR for c in coins):
|
if any(c[0] == Coins.DCR for c in coin_available):
|
||||||
coins.append((-6, 'Decred Wallet'))
|
coins.append((str(int(Coins.DCR)) + ",1", "Decred Wallet"))
|
||||||
if any(c[0] == Coins.LTC for c in coins):
|
if any(c[0] == Coins.LTC for c in coin_available):
|
||||||
coins.append((-5, 'Litecoin MWEB Wallet'))
|
coins.append((str(int(Coins.LTC)) + ",2", "Litecoin MWEB Wallet"))
|
||||||
if with_xmr:
|
if with_xmr:
|
||||||
coins.append((-2, 'Monero'))
|
coins.append((str(int(Coins.XMR)) + ",0", "Monero"))
|
||||||
coins.append((-3, 'Monero JSON'))
|
coins.append((str(int(Coins.XMR)) + ",1", "Monero JSON"))
|
||||||
coins.append((-4, 'Monero Wallet'))
|
coins.append((str(int(Coins.XMR)) + ",2", "Monero Wallet"))
|
||||||
|
if with_wow:
|
||||||
|
coins.append((str(int(Coins.WOW)) + ",0", "Wownero"))
|
||||||
|
coins.append((str(int(Coins.WOW)) + ",1", "Wownero JSON"))
|
||||||
|
coins.append((str(int(Coins.WOW)) + ",2", "Wownero Wallet"))
|
||||||
|
|
||||||
return self.render_template(template, {
|
return self.render_template(
|
||||||
'messages': messages,
|
template,
|
||||||
'err_messages': err_messages,
|
{
|
||||||
'coins': coins,
|
"messages": messages,
|
||||||
'coin_type': coin_id,
|
"err_messages": err_messages,
|
||||||
'call_type': call_type,
|
"coins": coins,
|
||||||
'result': result,
|
"coin_type": coin_type_selected,
|
||||||
'messages': messages,
|
"call_type": call_type,
|
||||||
'summary': summary,
|
"result": result,
|
||||||
})
|
"summary": summary,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def page_active(self, url_split, post_string):
|
def page_active(self, url_split, post_string):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
@@ -370,12 +406,24 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
active_swaps = swap_client.listSwapsInProgress()
|
active_swaps = swap_client.listSwapsInProgress()
|
||||||
summary = swap_client.getSummary()
|
summary = swap_client.getSummary()
|
||||||
|
|
||||||
template = env.get_template('active.html')
|
template = env.get_template("active.html")
|
||||||
return self.render_template(template, {
|
return self.render_template(
|
||||||
'refresh': 30,
|
template,
|
||||||
'active_swaps': [(s[0].hex(), s[1], strBidState(s[2]), strTxState(s[3]), strTxState(s[4])) for s in active_swaps],
|
{
|
||||||
'summary': summary,
|
"refresh": 30,
|
||||||
})
|
"active_swaps": [
|
||||||
|
(
|
||||||
|
s[0].hex(),
|
||||||
|
s[1],
|
||||||
|
strBidState(s[2]),
|
||||||
|
strTxState(s[3]),
|
||||||
|
strTxState(s[4]),
|
||||||
|
)
|
||||||
|
for s in active_swaps
|
||||||
|
],
|
||||||
|
"summary": summary,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def page_watched(self, url_split, post_string):
|
def page_watched(self, url_split, post_string):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
@@ -383,62 +431,68 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
watched_outputs, last_scanned = swap_client.listWatchedOutputs()
|
watched_outputs, last_scanned = swap_client.listWatchedOutputs()
|
||||||
summary = swap_client.getSummary()
|
summary = swap_client.getSummary()
|
||||||
|
|
||||||
template = env.get_template('watched.html')
|
template = env.get_template("watched.html")
|
||||||
return self.render_template(template, {
|
return self.render_template(
|
||||||
'refresh': 30,
|
template,
|
||||||
'last_scanned': [(getCoinName(ls[0]), ls[1]) for ls in last_scanned],
|
{
|
||||||
'watched_outputs': [(wo[1].hex(), getCoinName(wo[0]), wo[2], wo[3], int(wo[4])) for wo in watched_outputs],
|
"refresh": 30,
|
||||||
'summary': summary,
|
"last_scanned": [(getCoinName(ls[0]), ls[1]) for ls in last_scanned],
|
||||||
})
|
"watched_outputs": [
|
||||||
|
(wo[1].hex(), getCoinName(wo[0]), wo[2], wo[3], int(wo[4]))
|
||||||
|
for wo in watched_outputs
|
||||||
|
],
|
||||||
|
"summary": summary,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def page_shutdown(self, url_split, post_string):
|
def page_shutdown(self, url_split, post_string):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
|
|
||||||
if len(url_split) > 2:
|
if len(url_split) > 2:
|
||||||
token = url_split[2]
|
token = url_split[2]
|
||||||
expect_token = self.server.session_tokens.get('shutdown', None)
|
expect_token = self.server.session_tokens.get("shutdown", None)
|
||||||
if token != expect_token:
|
if token != expect_token:
|
||||||
return self.page_info('Unexpected token, still running.')
|
return self.page_info("Unexpected token, still running.")
|
||||||
|
|
||||||
swap_client.stopRunning()
|
swap_client.stopRunning()
|
||||||
|
|
||||||
return self.page_info('Shutting down')
|
return self.page_info("Shutting down")
|
||||||
|
|
||||||
def page_index(self, url_split):
|
def page_index(self, url_split):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
swap_client.checkSystemStatus()
|
swap_client.checkSystemStatus()
|
||||||
summary = swap_client.getSummary()
|
self.send_response(302)
|
||||||
template = env.get_template('index.html')
|
self.send_header("Location", "/offers")
|
||||||
return self.render_template(template, {
|
self.end_headers()
|
||||||
'refresh': 30,
|
return b""
|
||||||
'summary': summary,
|
|
||||||
'use_tor_proxy': swap_client.use_tor_proxy
|
|
||||||
})
|
|
||||||
|
|
||||||
def page_404(self, url_split):
|
def page_404(self, url_split):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
summary = swap_client.getSummary()
|
summary = swap_client.getSummary()
|
||||||
template = env.get_template('404.html')
|
template = env.get_template("404.html")
|
||||||
return self.render_template(template, {
|
return self.render_template(
|
||||||
'summary': summary,
|
template,
|
||||||
})
|
{
|
||||||
|
"summary": summary,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def putHeaders(self, status_code, content_type):
|
def putHeaders(self, status_code, content_type):
|
||||||
self.send_response(status_code)
|
self.send_response(status_code)
|
||||||
if self.server.allow_cors:
|
if self.server.allow_cors:
|
||||||
self.send_header('Access-Control-Allow-Origin', '*')
|
self.send_header("Access-Control-Allow-Origin", "*")
|
||||||
self.send_header('Content-Type', content_type)
|
self.send_header("Content-Type", content_type)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def handle_http(self, status_code, path, post_string='', is_json=False):
|
def handle_http(self, status_code, path, post_string="", is_json=False):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
parsed = parse.urlparse(self.path)
|
parsed = parse.urlparse(self.path)
|
||||||
url_split = parsed.path.split('/')
|
url_split = parsed.path.split("/")
|
||||||
if post_string == '' and len(parsed.query) > 0:
|
if post_string == "" and len(parsed.query) > 0:
|
||||||
post_string = parsed.query
|
post_string = parsed.query
|
||||||
if len(url_split) > 1 and url_split[1] == 'json':
|
if len(url_split) > 1 and url_split[1] == "json":
|
||||||
try:
|
try:
|
||||||
self.putHeaders(status_code, 'text/plain')
|
self.putHeaders(status_code, "text/plain")
|
||||||
func = js_url_to_function(url_split)
|
func = js_url_to_function(url_split)
|
||||||
return func(self, url_split, post_string, is_json)
|
return func(self, url_split, post_string, is_json)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@@ -446,37 +500,42 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
swap_client.log.error(traceback.format_exc())
|
swap_client.log.error(traceback.format_exc())
|
||||||
return js_error(self, str(ex))
|
return js_error(self, str(ex))
|
||||||
|
|
||||||
if len(url_split) > 1 and url_split[1] == 'static':
|
if len(url_split) > 1 and url_split[1] == "static":
|
||||||
try:
|
try:
|
||||||
static_path = os.path.join(os.path.dirname(__file__), 'static')
|
static_path = os.path.join(os.path.dirname(__file__), "static")
|
||||||
if len(url_split) > 3 and url_split[2] == 'sequence_diagrams':
|
if len(url_split) > 3 and url_split[2] == "sequence_diagrams":
|
||||||
with open(os.path.join(static_path, 'sequence_diagrams', url_split[3]), 'rb') as fp:
|
with open(
|
||||||
self.putHeaders(status_code, 'image/svg+xml')
|
os.path.join(static_path, "sequence_diagrams", url_split[3]),
|
||||||
|
"rb",
|
||||||
|
) as fp:
|
||||||
|
self.putHeaders(status_code, "image/svg+xml")
|
||||||
return fp.read()
|
return fp.read()
|
||||||
elif len(url_split) > 3 and url_split[2] == 'images':
|
elif len(url_split) > 3 and url_split[2] == "images":
|
||||||
filename = os.path.join(*url_split[3:])
|
filename = os.path.join(*url_split[3:])
|
||||||
_, extension = os.path.splitext(filename)
|
_, extension = os.path.splitext(filename)
|
||||||
mime_type = {
|
mime_type = {
|
||||||
'.svg': 'image/svg+xml',
|
".svg": "image/svg+xml",
|
||||||
'.png': 'image/png',
|
".png": "image/png",
|
||||||
'.jpg': 'image/jpeg',
|
".jpg": "image/jpeg",
|
||||||
'.gif': 'image/gif',
|
".gif": "image/gif",
|
||||||
'.ico': 'image/x-icon',
|
".ico": "image/x-icon",
|
||||||
}.get(extension, '')
|
}.get(extension, "")
|
||||||
if mime_type == '':
|
if mime_type == "":
|
||||||
raise ValueError('Unknown file type ' + filename)
|
raise ValueError("Unknown file type " + filename)
|
||||||
with open(os.path.join(static_path, 'images', filename), 'rb') as fp:
|
with open(
|
||||||
|
os.path.join(static_path, "images", filename), "rb"
|
||||||
|
) as fp:
|
||||||
self.putHeaders(status_code, mime_type)
|
self.putHeaders(status_code, mime_type)
|
||||||
return fp.read()
|
return fp.read()
|
||||||
elif len(url_split) > 3 and url_split[2] == 'css':
|
elif len(url_split) > 3 and url_split[2] == "css":
|
||||||
filename = os.path.join(*url_split[3:])
|
filename = os.path.join(*url_split[3:])
|
||||||
with open(os.path.join(static_path, 'css', filename), 'rb') as fp:
|
with open(os.path.join(static_path, "css", filename), "rb") as fp:
|
||||||
self.putHeaders(status_code, 'text/css; charset=utf-8')
|
self.putHeaders(status_code, "text/css; charset=utf-8")
|
||||||
return fp.read()
|
return fp.read()
|
||||||
elif len(url_split) > 3 and url_split[2] == 'js':
|
elif len(url_split) > 3 and url_split[2] == "js":
|
||||||
filename = os.path.join(*url_split[3:])
|
filename = os.path.join(*url_split[3:])
|
||||||
with open(os.path.join(static_path, 'js', filename), 'rb') as fp:
|
with open(os.path.join(static_path, "js", filename), "rb") as fp:
|
||||||
self.putHeaders(status_code, 'application/javascript')
|
self.putHeaders(status_code, "application/javascript")
|
||||||
return fp.read()
|
return fp.read()
|
||||||
else:
|
else:
|
||||||
return self.page_404(url_split)
|
return self.page_404(url_split)
|
||||||
@@ -491,63 +550,61 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
if len(url_split) > 1:
|
if len(url_split) > 1:
|
||||||
page = url_split[1]
|
page = url_split[1]
|
||||||
|
|
||||||
if page == 'active':
|
if page == "active":
|
||||||
return self.page_active(url_split, post_string)
|
return self.page_active(url_split, post_string)
|
||||||
if page == 'wallets':
|
if page == "wallets":
|
||||||
return page_wallets(self, url_split, post_string)
|
return page_wallets(self, url_split, post_string)
|
||||||
if page == 'wallet':
|
if page == "wallet":
|
||||||
return page_wallet(self, url_split, post_string)
|
return page_wallet(self, url_split, post_string)
|
||||||
if page == 'settings':
|
if page == "settings":
|
||||||
return page_settings(self, url_split, post_string)
|
return page_settings(self, url_split, post_string)
|
||||||
if page == 'error':
|
if page == "error":
|
||||||
return self.page_error(url_split, post_string)
|
return self.page_error(url_split, post_string)
|
||||||
if page == 'info':
|
if page == "info":
|
||||||
return self.page_info(url_split, post_string)
|
return self.page_info(url_split, post_string)
|
||||||
if page == 'rpc':
|
if page == "rpc":
|
||||||
return self.page_rpc(url_split, post_string)
|
return self.page_rpc(url_split, post_string)
|
||||||
if page == 'debug':
|
if page == "debug":
|
||||||
return page_debug(self, url_split, post_string)
|
return page_debug(self, url_split, post_string)
|
||||||
if page == 'explorers':
|
if page == "explorers":
|
||||||
return self.page_explorers(url_split, post_string)
|
return self.page_explorers(url_split, post_string)
|
||||||
if page == 'offer':
|
if page == "offer":
|
||||||
return page_offer(self, url_split, post_string)
|
return page_offer(self, url_split, post_string)
|
||||||
if page == 'offers':
|
if page == "offers":
|
||||||
return page_offers(self, url_split, post_string)
|
return page_offers(self, url_split, post_string)
|
||||||
if page == 'newoffer':
|
if page == "newoffer":
|
||||||
return page_newoffer(self, url_split, post_string)
|
return page_newoffer(self, url_split, post_string)
|
||||||
if page == 'sentoffers':
|
if page == "sentoffers":
|
||||||
return page_offers(self, url_split, post_string, sent=True)
|
return page_offers(self, url_split, post_string, sent=True)
|
||||||
if page == 'bid':
|
if page == "bid":
|
||||||
return page_bid(self, url_split, post_string)
|
return page_bid(self, url_split, post_string)
|
||||||
if page == 'receivedbids':
|
if page == "bids":
|
||||||
return page_bids(self, url_split, post_string, received=True)
|
return page_bids(self, url_split, post_string)
|
||||||
if page == 'sentbids':
|
if page == "availablebids":
|
||||||
return page_bids(self, url_split, post_string, sent=True)
|
|
||||||
if page == 'availablebids':
|
|
||||||
return page_bids(self, url_split, post_string, available=True)
|
return page_bids(self, url_split, post_string, available=True)
|
||||||
if page == 'watched':
|
if page == "watched":
|
||||||
return self.page_watched(url_split, post_string)
|
return self.page_watched(url_split, post_string)
|
||||||
if page == 'smsgaddresses':
|
if page == "smsgaddresses":
|
||||||
return page_smsgaddresses(self, url_split, post_string)
|
return page_smsgaddresses(self, url_split, post_string)
|
||||||
if page == 'identity':
|
if page == "identity":
|
||||||
return page_identity(self, url_split, post_string)
|
return page_identity(self, url_split, post_string)
|
||||||
if page == 'tor':
|
if page == "tor":
|
||||||
return page_tor(self, url_split, post_string)
|
return page_tor(self, url_split, post_string)
|
||||||
if page == 'automation':
|
if page == "automation":
|
||||||
return page_automation_strategies(self, url_split, post_string)
|
return page_automation_strategies(self, url_split, post_string)
|
||||||
if page == 'automationstrategy':
|
if page == "automationstrategy":
|
||||||
return page_automation_strategy(self, url_split, post_string)
|
return page_automation_strategy(self, url_split, post_string)
|
||||||
if page == 'newautomationstrategy':
|
if page == "newautomationstrategy":
|
||||||
return page_automation_strategy_new(self, url_split, post_string)
|
return page_automation_strategy_new(self, url_split, post_string)
|
||||||
if page == 'shutdown':
|
if page == "shutdown":
|
||||||
return self.page_shutdown(url_split, post_string)
|
return self.page_shutdown(url_split, post_string)
|
||||||
if page == 'changepassword':
|
if page == "changepassword":
|
||||||
return page_changepassword(self, url_split, post_string)
|
return page_changepassword(self, url_split, post_string)
|
||||||
if page == 'unlock':
|
if page == "unlock":
|
||||||
return page_unlock(self, url_split, post_string)
|
return page_unlock(self, url_split, post_string)
|
||||||
if page == 'lock':
|
if page == "lock":
|
||||||
return page_lock(self, url_split, post_string)
|
return page_lock(self, url_split, post_string)
|
||||||
if page != '':
|
if page != "":
|
||||||
return self.page_404(url_split)
|
return self.page_404(url_split)
|
||||||
return self.page_index(url_split)
|
return self.page_index(url_split)
|
||||||
except LockedCoinError:
|
except LockedCoinError:
|
||||||
@@ -562,20 +619,20 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
self.wfile.write(response)
|
self.wfile.write(response)
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
post_string = self.rfile.read(int(self.headers.get('Content-Length')))
|
post_string = self.rfile.read(int(self.headers.get("Content-Length")))
|
||||||
|
|
||||||
is_json = True if 'json' in self.headers.get('Content-Type', '') else False
|
is_json = True if "json" in self.headers.get("Content-Type", "") else False
|
||||||
response = self.handle_http(200, self.path, post_string, is_json)
|
response = self.handle_http(200, self.path, post_string, is_json)
|
||||||
self.wfile.write(response)
|
self.wfile.write(response)
|
||||||
|
|
||||||
def do_HEAD(self):
|
def do_HEAD(self):
|
||||||
self.putHeaders(200, 'text/html')
|
self.putHeaders(200, "text/html")
|
||||||
|
|
||||||
def do_OPTIONS(self):
|
def do_OPTIONS(self):
|
||||||
self.send_response(200, 'ok')
|
self.send_response(200, "ok")
|
||||||
if self.server.allow_cors:
|
if self.server.allow_cors:
|
||||||
self.send_header('Access-Control-Allow-Origin', '*')
|
self.send_header("Access-Control-Allow-Origin", "*")
|
||||||
self.send_header('Access-Control-Allow-Headers', '*')
|
self.send_header("Access-Control-Allow-Headers", "*")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
|
|
||||||
@@ -589,7 +646,7 @@ class HttpThread(threading.Thread, HTTPServer):
|
|||||||
self.port_no = port_no
|
self.port_no = port_no
|
||||||
self.allow_cors = allow_cors
|
self.allow_cors = allow_cors
|
||||||
self.swap_client = swap_client
|
self.swap_client = swap_client
|
||||||
self.title = 'BasicSwap - ' + __version__
|
self.title = "BasicSwap - " + __version__
|
||||||
self.last_form_id = dict()
|
self.last_form_id = dict()
|
||||||
self.session_tokens = dict()
|
self.session_tokens = dict()
|
||||||
self.env = env
|
self.env = env
|
||||||
@@ -604,9 +661,9 @@ class HttpThread(threading.Thread, HTTPServer):
|
|||||||
# Send fake request
|
# Send fake request
|
||||||
conn = http.client.HTTPConnection(self.host_name, self.port_no)
|
conn = http.client.HTTPConnection(self.host_name, self.port_no)
|
||||||
conn.connect()
|
conn.connect()
|
||||||
conn.request('GET', '/none')
|
conn.request("GET", "/none")
|
||||||
response = conn.getresponse()
|
response = conn.getresponse()
|
||||||
data = response.read()
|
_ = response.read()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def serve_forever(self):
|
def serve_forever(self):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
|
# Copyright (c) 2025 The Basicswap developers
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -14,7 +15,8 @@ from basicswap.chainparams import (
|
|||||||
)
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
ensure,
|
ensure,
|
||||||
i2b, b2i,
|
i2b,
|
||||||
|
b2i,
|
||||||
make_int,
|
make_int,
|
||||||
format_amount,
|
format_amount,
|
||||||
TemporaryError,
|
TemporaryError,
|
||||||
@@ -26,9 +28,7 @@ from basicswap.util.ecc import (
|
|||||||
ep,
|
ep,
|
||||||
getSecretInt,
|
getSecretInt,
|
||||||
)
|
)
|
||||||
from coincurve.dleag import (
|
from coincurve.dleag import verify_secp256k1_point
|
||||||
verify_secp256k1_point
|
|
||||||
)
|
|
||||||
from coincurve.keys import (
|
from coincurve.keys import (
|
||||||
PublicKey,
|
PublicKey,
|
||||||
)
|
)
|
||||||
@@ -52,6 +52,11 @@ class CoinInterface:
|
|||||||
self.setDefaults()
|
self.setDefaults()
|
||||||
self._network = network
|
self._network = network
|
||||||
self._mx_wallet = threading.Lock()
|
self._mx_wallet = threading.Lock()
|
||||||
|
self._altruistic = True
|
||||||
|
|
||||||
|
def interface_type(self) -> int:
|
||||||
|
# coin_type() returns the base coin type, interface_type() returns the coin+balance type.
|
||||||
|
return self.coin_type()
|
||||||
|
|
||||||
def setDefaults(self):
|
def setDefaults(self):
|
||||||
self._unknown_wallet_seed = True
|
self._unknown_wallet_seed = True
|
||||||
@@ -66,33 +71,33 @@ class CoinInterface:
|
|||||||
|
|
||||||
def coin_name(self) -> str:
|
def coin_name(self) -> str:
|
||||||
coin_chainparams = chainparams[self.coin_type()]
|
coin_chainparams = chainparams[self.coin_type()]
|
||||||
if coin_chainparams.get('use_ticker_as_name', False):
|
if "display_name" in coin_chainparams:
|
||||||
return coin_chainparams['ticker']
|
return coin_chainparams["display_name"]
|
||||||
return coin_chainparams['name'].capitalize()
|
return coin_chainparams["name"].capitalize()
|
||||||
|
|
||||||
def ticker(self) -> str:
|
def ticker(self) -> str:
|
||||||
ticker = chainparams[self.coin_type()]['ticker']
|
ticker = chainparams[self.coin_type()]["ticker"]
|
||||||
if self._network == 'testnet':
|
if self._network == "testnet":
|
||||||
ticker = 't' + ticker
|
ticker = "t" + ticker
|
||||||
elif self._network == 'regtest':
|
elif self._network == "regtest":
|
||||||
ticker = 'rt' + ticker
|
ticker = "rt" + ticker
|
||||||
return ticker
|
return ticker
|
||||||
|
|
||||||
def getExchangeTicker(self, exchange_name: str) -> str:
|
def getExchangeTicker(self, exchange_name: str) -> str:
|
||||||
return chainparams[self.coin_type()]['ticker']
|
return chainparams[self.coin_type()]["ticker"]
|
||||||
|
|
||||||
def getExchangeName(self, exchange_name: str) -> str:
|
def getExchangeName(self, exchange_name: str) -> str:
|
||||||
return chainparams[self.coin_type()]['name']
|
return chainparams[self.coin_type()]["name"]
|
||||||
|
|
||||||
def ticker_mainnet(self) -> str:
|
def ticker_mainnet(self) -> str:
|
||||||
ticker = chainparams[self.coin_type()]['ticker']
|
ticker = chainparams[self.coin_type()]["ticker"]
|
||||||
return ticker
|
return ticker
|
||||||
|
|
||||||
def min_amount(self) -> int:
|
def min_amount(self) -> int:
|
||||||
return chainparams[self.coin_type()][self._network]['min_amount']
|
return chainparams[self.coin_type()][self._network]["min_amount"]
|
||||||
|
|
||||||
def max_amount(self) -> int:
|
def max_amount(self) -> int:
|
||||||
return chainparams[self.coin_type()][self._network]['max_amount']
|
return chainparams[self.coin_type()][self._network]["max_amount"]
|
||||||
|
|
||||||
def setWalletSeedWarning(self, value: bool) -> None:
|
def setWalletSeedWarning(self, value: bool) -> None:
|
||||||
self._unknown_wallet_seed = value
|
self._unknown_wallet_seed = value
|
||||||
@@ -110,7 +115,7 @@ class CoinInterface:
|
|||||||
return chainparams[self.coin_type()][self._network]
|
return chainparams[self.coin_type()][self._network]
|
||||||
|
|
||||||
def has_segwit(self) -> bool:
|
def has_segwit(self) -> bool:
|
||||||
return chainparams[self.coin_type()].get('has_segwit', True)
|
return chainparams[self.coin_type()].get("has_segwit", True)
|
||||||
|
|
||||||
def use_p2shp2wsh(self) -> bool:
|
def use_p2shp2wsh(self) -> bool:
|
||||||
# p2sh-p2wsh
|
# p2sh-p2wsh
|
||||||
@@ -120,24 +125,26 @@ class CoinInterface:
|
|||||||
if isinstance(ex, TemporaryError):
|
if isinstance(ex, TemporaryError):
|
||||||
return True
|
return True
|
||||||
str_error: str = str(ex).lower()
|
str_error: str = str(ex).lower()
|
||||||
if 'not enough unlocked money' in str_error:
|
if "not enough unlocked money" in str_error:
|
||||||
return True
|
return True
|
||||||
if 'no unlocked balance' in str_error:
|
if "no unlocked balance" in str_error:
|
||||||
return True
|
return True
|
||||||
if 'transaction was rejected by daemon' in str_error:
|
if "transaction was rejected by daemon" in str_error:
|
||||||
return True
|
return True
|
||||||
if 'invalid unlocked_balance' in str_error:
|
if "invalid unlocked_balance" in str_error:
|
||||||
return True
|
return True
|
||||||
if 'daemon is busy' in str_error:
|
if "daemon is busy" in str_error:
|
||||||
return True
|
return True
|
||||||
if 'timed out' in str_error:
|
if "timed out" in str_error:
|
||||||
return True
|
return True
|
||||||
if 'request-sent' in str_error:
|
if "request-sent" in str_error:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def setConfTarget(self, new_conf_target: int) -> None:
|
def setConfTarget(self, new_conf_target: int) -> None:
|
||||||
ensure(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 walletRestoreHeight(self) -> int:
|
def walletRestoreHeight(self) -> int:
|
||||||
@@ -166,31 +173,19 @@ class CoinInterface:
|
|||||||
def checkWallets(self) -> int:
|
def checkWallets(self) -> int:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
def altruistic(self) -> bool:
|
||||||
|
return self._altruistic
|
||||||
|
|
||||||
class AdaptorSigInterface():
|
|
||||||
|
class AdaptorSigInterface:
|
||||||
def getScriptLockTxDummyWitness(self, script: bytes):
|
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||||
return [
|
return [b"", bytes(72), bytes(72), bytes(len(script))]
|
||||||
b'',
|
|
||||||
bytes(72),
|
|
||||||
bytes(72),
|
|
||||||
bytes(len(script))
|
|
||||||
]
|
|
||||||
|
|
||||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
||||||
return [
|
return [b"", bytes(72), bytes(72), bytes((1,)), bytes(len(script))]
|
||||||
b'',
|
|
||||||
bytes(72),
|
|
||||||
bytes(72),
|
|
||||||
bytes((1,)),
|
|
||||||
bytes(len(script))
|
|
||||||
]
|
|
||||||
|
|
||||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
|
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
|
||||||
return [
|
return [bytes(72), b"", bytes(len(script))]
|
||||||
bytes(72),
|
|
||||||
b'',
|
|
||||||
bytes(len(script))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
||||||
@@ -198,7 +193,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
|||||||
def curve_type():
|
def curve_type():
|
||||||
return Curves.secp256k1
|
return Curves.secp256k1
|
||||||
|
|
||||||
def getNewSecretKey(self) -> bytes:
|
def getNewRandomKey(self) -> bytes:
|
||||||
return i2b(getSecretInt())
|
return i2b(getSecretInt())
|
||||||
|
|
||||||
def getPubkey(self, privkey: bytes) -> bytes:
|
def getPubkey(self, privkey: bytes) -> bytes:
|
||||||
@@ -209,7 +204,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
|||||||
|
|
||||||
def verifyKey(self, k: bytes) -> bool:
|
def verifyKey(self, k: bytes) -> bool:
|
||||||
i = b2i(k)
|
i = b2i(k)
|
||||||
return (i < ep.o and i > 0)
|
return i < ep.o and i > 0
|
||||||
|
|
||||||
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
||||||
return verify_secp256k1_point(pubkey_bytes)
|
return verify_secp256k1_point(pubkey_bytes)
|
||||||
|
|||||||
1136
basicswap/interface/bch.py
Normal file
247
basicswap/interface/contrib/bch_test_framework/cashaddress.py
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||||
|
|
||||||
|
def polymod(values):
|
||||||
|
chk = 1
|
||||||
|
generator = [
|
||||||
|
(0x01, 0x98F2BC8E61),
|
||||||
|
(0x02, 0x79B76D99E2),
|
||||||
|
(0x04, 0xF33E5FB3C4),
|
||||||
|
(0x08, 0xAE2EABE2A8),
|
||||||
|
(0x10, 0x1E4F43E470),
|
||||||
|
]
|
||||||
|
for value in values:
|
||||||
|
top = chk >> 35
|
||||||
|
chk = ((chk & 0x07FFFFFFFF) << 5) ^ value
|
||||||
|
for i in generator:
|
||||||
|
if top & i[0] != 0:
|
||||||
|
chk ^= i[1]
|
||||||
|
return chk ^ 1
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_checksum(prefix, payload):
|
||||||
|
poly = polymod(prefix_expand(prefix) + payload + [0, 0, 0, 0, 0, 0, 0, 0])
|
||||||
|
out = list()
|
||||||
|
for i in range(8):
|
||||||
|
out.append((poly >> 5 * (7 - i)) & 0x1F)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def verify_checksum(prefix, payload):
|
||||||
|
return polymod(prefix_expand(prefix) + payload) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def b32decode(inputs):
|
||||||
|
out = list()
|
||||||
|
for letter in inputs:
|
||||||
|
out.append(CHARSET.find(letter))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def b32encode(inputs):
|
||||||
|
out = ""
|
||||||
|
for char_code in inputs:
|
||||||
|
out += CHARSET[char_code]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def convertbits(data, frombits, tobits, pad=True):
|
||||||
|
acc = 0
|
||||||
|
bits = 0
|
||||||
|
ret = []
|
||||||
|
maxv = (1 << tobits) - 1
|
||||||
|
max_acc = (1 << (frombits + tobits - 1)) - 1
|
||||||
|
for value in data:
|
||||||
|
if value < 0 or (value >> frombits):
|
||||||
|
return None
|
||||||
|
acc = ((acc << frombits) | value) & max_acc
|
||||||
|
bits += frombits
|
||||||
|
while bits >= tobits:
|
||||||
|
bits -= tobits
|
||||||
|
ret.append((acc >> bits) & maxv)
|
||||||
|
if pad:
|
||||||
|
if bits:
|
||||||
|
ret.append((acc << (tobits - bits)) & maxv)
|
||||||
|
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
|
||||||
|
return None
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def prefix_expand(prefix):
|
||||||
|
return [ord(x) & 0x1F for x in prefix] + [0]
|
||||||
|
|
||||||
|
|
||||||
|
class Address:
|
||||||
|
"""
|
||||||
|
Class to handle CashAddr.
|
||||||
|
|
||||||
|
:param version: Version of CashAddr
|
||||||
|
:type version: ``str``
|
||||||
|
:param payload: Payload of CashAddr as int list of the bytearray
|
||||||
|
:type payload: ``list`` of ``int``
|
||||||
|
"""
|
||||||
|
|
||||||
|
VERSIONS = {
|
||||||
|
"P2SH20": {"prefix": "bitcoincash", "version_bit": 8, "network": "mainnet"},
|
||||||
|
"P2SH32": {"prefix": "bitcoincash", "version_bit": 11, "network": "mainnet"},
|
||||||
|
"P2PKH": {"prefix": "bitcoincash", "version_bit": 0, "network": "mainnet"},
|
||||||
|
"P2SH20-TESTNET": {"prefix": "bchtest", "version_bit": 8, "network": "testnet"},
|
||||||
|
"P2SH32-TESTNET": {
|
||||||
|
"prefix": "bchtest",
|
||||||
|
"version_bit": 11,
|
||||||
|
"network": "testnet",
|
||||||
|
},
|
||||||
|
"P2PKH-TESTNET": {"prefix": "bchtest", "version_bit": 0, "network": "testnet"},
|
||||||
|
"P2SH20-REGTEST": {"prefix": "bchreg", "version_bit": 8, "network": "regtest"},
|
||||||
|
"P2SH32-REGTEST": {"prefix": "bchreg", "version_bit": 11, "network": "regtest"},
|
||||||
|
"P2PKH-REGTEST": {"prefix": "bchreg", "version_bit": 0, "network": "regtest"},
|
||||||
|
"P2SH20-CATKN": {
|
||||||
|
"prefix": "bitcoincash",
|
||||||
|
"version_bit": 24,
|
||||||
|
"network": "mainnet",
|
||||||
|
},
|
||||||
|
"P2SH32-CATKN": {
|
||||||
|
"prefix": "bitcoincash",
|
||||||
|
"version_bit": 27,
|
||||||
|
"network": "mainnet",
|
||||||
|
},
|
||||||
|
"P2PKH-CATKN": {
|
||||||
|
"prefix": "bitcoincash",
|
||||||
|
"version_bit": 16,
|
||||||
|
"network": "mainnet",
|
||||||
|
},
|
||||||
|
"P2SH20-CATKN-TESTNET": {
|
||||||
|
"prefix": "bchtest",
|
||||||
|
"version_bit": 24,
|
||||||
|
"network": "testnet",
|
||||||
|
},
|
||||||
|
"P2SH32-CATKN-TESTNET": {
|
||||||
|
"prefix": "bchtest",
|
||||||
|
"version_bit": 27,
|
||||||
|
"network": "testnet",
|
||||||
|
},
|
||||||
|
"P2PKH-CATKN-TESTNET": {
|
||||||
|
"prefix": "bchtest",
|
||||||
|
"version_bit": 16,
|
||||||
|
"network": "testnet",
|
||||||
|
},
|
||||||
|
"P2SH20-CATKN-REGTEST": {
|
||||||
|
"prefix": "bchreg",
|
||||||
|
"version_bit": 24,
|
||||||
|
"network": "regtest",
|
||||||
|
},
|
||||||
|
"P2SH32-CATKN-REGTEST": {
|
||||||
|
"prefix": "bchreg",
|
||||||
|
"version_bit": 27,
|
||||||
|
"network": "regtest",
|
||||||
|
},
|
||||||
|
"P2PKH-CATKN-REGTEST": {
|
||||||
|
"prefix": "bchreg",
|
||||||
|
"version_bit": 16,
|
||||||
|
"network": "regtest",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSION_SUFFIXES = {"bitcoincash": "", "bchtest": "-TESTNET", "bchreg": "-REGTEST"}
|
||||||
|
|
||||||
|
ADDRESS_TYPES = {
|
||||||
|
0: "P2PKH",
|
||||||
|
8: "P2SH20",
|
||||||
|
11: "P2SH32",
|
||||||
|
16: "P2PKH-CATKN",
|
||||||
|
24: "P2SH20-CATKN",
|
||||||
|
27: "P2SH32-CATKN",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, version, payload):
|
||||||
|
if version not in Address.VERSIONS:
|
||||||
|
raise ValueError("Invalid address version provided")
|
||||||
|
|
||||||
|
self.version = version
|
||||||
|
self.payload = payload
|
||||||
|
self.prefix = Address.VERSIONS[self.version]["prefix"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return (
|
||||||
|
f"version: {self.version}\npayload: {self.payload}\nprefix: {self.prefix}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Address('{self.cash_address()}')"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
return self.cash_address() == other
|
||||||
|
elif isinstance(other, Address):
|
||||||
|
return self.cash_address() == other.cash_address()
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Address can be compared to a string address"
|
||||||
|
" or an instance of Address"
|
||||||
|
)
|
||||||
|
|
||||||
|
def cash_address(self):
|
||||||
|
"""
|
||||||
|
Generate CashAddr of the Address
|
||||||
|
|
||||||
|
:rtype: ``str``
|
||||||
|
"""
|
||||||
|
version_bit = Address.VERSIONS[self.version]["version_bit"]
|
||||||
|
payload = [version_bit] + list(self.payload)
|
||||||
|
payload = convertbits(payload, 8, 5)
|
||||||
|
checksum = calculate_checksum(self.prefix, payload)
|
||||||
|
return self.prefix + ":" + b32encode(payload + checksum)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_string(address):
|
||||||
|
"""
|
||||||
|
Generate Address from a cashadress string
|
||||||
|
|
||||||
|
:param scriptcode: The cashaddress string
|
||||||
|
:type scriptcode: ``str``
|
||||||
|
:returns: Instance of :class:~bitcash.cashaddress.Address
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
address = str(address)
|
||||||
|
except Exception:
|
||||||
|
raise ValueError("Expected string as input")
|
||||||
|
|
||||||
|
if address.upper() != address and address.lower() != address:
|
||||||
|
raise ValueError(
|
||||||
|
"Cash address contains uppercase and lowercase characters: " + address
|
||||||
|
)
|
||||||
|
|
||||||
|
address = address.lower()
|
||||||
|
colon_count = address.count(":")
|
||||||
|
if colon_count == 0:
|
||||||
|
raise ValueError("Cash address is missing prefix")
|
||||||
|
if colon_count > 1:
|
||||||
|
raise ValueError("Cash address contains more than one colon character")
|
||||||
|
|
||||||
|
prefix, base32string = address.split(":")
|
||||||
|
decoded = b32decode(base32string)
|
||||||
|
|
||||||
|
if not verify_checksum(prefix, decoded):
|
||||||
|
raise ValueError(
|
||||||
|
"Bad cash address checksum for address {}".format(address)
|
||||||
|
)
|
||||||
|
converted = convertbits(decoded, 5, 8)
|
||||||
|
|
||||||
|
try:
|
||||||
|
version = Address.ADDRESS_TYPES[converted[0]]
|
||||||
|
except Exception:
|
||||||
|
raise ValueError("Could not determine address version")
|
||||||
|
|
||||||
|
version += Address.VERSION_SUFFIXES[prefix]
|
||||||
|
|
||||||
|
payload = converted[1:-6]
|
||||||
|
return Address(version, payload)
|
||||||
|
|
||||||
|
class TestFrameworkScript(unittest.TestCase):
|
||||||
|
def test_base58encodedecode(self):
|
||||||
|
def check_cashaddress(address: str):
|
||||||
|
self.assertEqual(Address.from_string(address).cash_address(), address)
|
||||||
|
|
||||||
|
check_cashaddress("bitcoincash:qzfyvx77v2pmgc0vulwlfkl3uzjgh5gnmqk5hhyaa6")
|
||||||
43
basicswap/interface/contrib/bch_test_framework/script.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
|
||||||
|
from basicswap.contrib.test_framework.script import CScriptOp
|
||||||
|
|
||||||
|
|
||||||
|
OP_TXINPUTCOUNT = CScriptOp(0xc3)
|
||||||
|
OP_1 = CScriptOp(0x51)
|
||||||
|
OP_NUMEQUALVERIFY = CScriptOp(0x9d)
|
||||||
|
OP_TXOUTPUTCOUNT = CScriptOp(0xc4)
|
||||||
|
OP_0 = CScriptOp(0x00)
|
||||||
|
OP_UTXOVALUE = CScriptOp(0xc6)
|
||||||
|
OP_OUTPUTVALUE = CScriptOp(0xcc)
|
||||||
|
OP_SUB = CScriptOp(0x94)
|
||||||
|
OP_UTXOTOKENCATEGORY = CScriptOp(0xce)
|
||||||
|
OP_OUTPUTTOKENCATEGORY = CScriptOp(0xd1)
|
||||||
|
OP_EQUALVERIFY = CScriptOp(0x88)
|
||||||
|
OP_UTXOTOKENCOMMITMENT = CScriptOp(0xcf)
|
||||||
|
OP_OUTPUTTOKENCOMMITMENT = CScriptOp(0xd2)
|
||||||
|
OP_UTXOTOKENAMOUNT = CScriptOp(0xd0)
|
||||||
|
OP_OUTPUTTOKENAMOUNT = CScriptOp(0xd3)
|
||||||
|
OP_INPUTSEQUENCENUMBER = CScriptOp(0xcb)
|
||||||
|
OP_NOTIF = CScriptOp(0x64)
|
||||||
|
OP_OUTPUTBYTECODE = CScriptOp(0xcd)
|
||||||
|
OP_OVER = CScriptOp(0x78)
|
||||||
|
OP_CHECKDATASIG = CScriptOp(0xba)
|
||||||
|
OP_CHECKDATASIGVERIFY = CScriptOp(0xbb)
|
||||||
|
OP_ELSE = CScriptOp(0x67)
|
||||||
|
OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2)
|
||||||
|
OP_DROP = CScriptOp(0x75)
|
||||||
|
OP_EQUAL = CScriptOp(0x87)
|
||||||
|
OP_ENDIF = CScriptOp(0x68)
|
||||||
|
OP_HASH256 = CScriptOp(0xaa)
|
||||||
|
OP_PUSHBYTES_32 = CScriptOp(0x20)
|
||||||
|
OP_DUP = CScriptOp(0x76)
|
||||||
|
OP_HASH160 = CScriptOp(0xa9)
|
||||||
|
OP_CHECKSIG = CScriptOp(0xac)
|
||||||
|
OP_SHA256 = CScriptOp(0xa8)
|
||||||
|
OP_VERIFY = CScriptOp(0x69)
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# 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 .btc import BTCInterface
|
from .btc import BTCInterface
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.util.address import decodeAddress
|
from basicswap.util.address import decodeAddress
|
||||||
from mnemonic import Mnemonic
|
from basicswap.contrib.mnemonic import Mnemonic
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import (
|
||||||
CScript,
|
CScript,
|
||||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
OP_DUP,
|
||||||
|
OP_HASH160,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
OP_CHECKSIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -22,41 +26,62 @@ class DASHInterface(BTCInterface):
|
|||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(coin_settings, network, swap_client)
|
super().__init__(coin_settings, network, swap_client)
|
||||||
self._wallet_passphrase = ''
|
self._wallet_passphrase = ""
|
||||||
self._have_checked_seed = False
|
self._have_checked_seed = False
|
||||||
|
|
||||||
def seedToMnemonic(self, key: bytes) -> str:
|
self._wallet_v20_compatible = (
|
||||||
return Mnemonic('english').to_mnemonic(key)
|
False
|
||||||
|
if not swap_client
|
||||||
def initialiseWallet(self, key: bytes):
|
else swap_client.getChainClientSettings(self.coin_type()).get(
|
||||||
words = self.seedToMnemonic(key)
|
"wallet_v20_compatible", False
|
||||||
|
)
|
||||||
mnemonic_passphrase = ''
|
)
|
||||||
self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
|
|
||||||
self._have_checked_seed = False
|
|
||||||
if self._wallet_passphrase != '':
|
|
||||||
self.unlockWallet(self._wallet_passphrase)
|
|
||||||
|
|
||||||
def decodeAddress(self, address: str) -> bytes:
|
def decodeAddress(self, address: str) -> bytes:
|
||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
|
|
||||||
def checkExpectedSeed(self, key_hash: str):
|
def getWalletSeedID(self) -> str:
|
||||||
try:
|
hdseed: str = self.rpc_wallet("dumphdinfo")["hdseed"]
|
||||||
rv = self.rpc_wallet('dumphdinfo')
|
return self.getSeedHash(bytes.fromhex(hdseed)).hex()
|
||||||
entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' '))
|
|
||||||
|
def entropyToMnemonic(self, key: bytes) -> None:
|
||||||
|
return Mnemonic("english").to_mnemonic(key)
|
||||||
|
|
||||||
|
def initialiseWallet(self, key_bytes: bytes) -> None:
|
||||||
|
self._have_checked_seed = False
|
||||||
|
if self._wallet_v20_compatible:
|
||||||
|
self._log.warning("Generating wallet compatible with v20 seed.")
|
||||||
|
words = self.entropyToMnemonic(key_bytes)
|
||||||
|
mnemonic_passphrase = ""
|
||||||
|
self.rpc_wallet(
|
||||||
|
"upgradetohd", [words, mnemonic_passphrase, self._wallet_passphrase]
|
||||||
|
)
|
||||||
|
self._have_checked_seed = False
|
||||||
|
if self._wallet_passphrase != "":
|
||||||
|
self.unlockWallet(self._wallet_passphrase)
|
||||||
|
return
|
||||||
|
|
||||||
|
key_wif = self.encodeKey(key_bytes)
|
||||||
|
self.rpc_wallet("sethdseed", [True, key_wif])
|
||||||
|
|
||||||
|
def checkExpectedSeed(self, expect_seedid: str) -> bool:
|
||||||
|
self._expect_seedid_hex = expect_seedid
|
||||||
|
rv = self.rpc_wallet("dumphdinfo")
|
||||||
|
if rv["mnemonic"] != "":
|
||||||
|
entropy = Mnemonic("english").to_entropy(rv["mnemonic"].split(" "))
|
||||||
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
|
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
|
||||||
|
have_expected_seed: bool = expect_seedid == entropy_hash
|
||||||
|
else:
|
||||||
|
have_expected_seed: bool = expect_seedid == self.getWalletSeedID()
|
||||||
self._have_checked_seed = True
|
self._have_checked_seed = True
|
||||||
return entropy_hash == key_hash
|
return have_expected_seed
|
||||||
except Exception as e:
|
|
||||||
self._log.warning('checkExpectedSeed failed: {}'.format(str(e)))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee, False, False, self._conf_target]
|
params = [addr_to, value, "", "", subfee, False, False, self._conf_target]
|
||||||
return self.rpc_wallet('sendtoaddress', params)
|
return self.rpc_wallet("sendtoaddress", params)
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc_wallet('getwalletinfo')['balance'])
|
return self.make_int(self.rpc_wallet("getwalletinfo")["balance"])
|
||||||
|
|
||||||
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
# Return P2PKH
|
# Return P2PKH
|
||||||
@@ -66,29 +91,38 @@ class DASHInterface(BTCInterface):
|
|||||||
add_bytes = 107
|
add_bytes = 107
|
||||||
size = len(tx.serialize_with_witness()) + add_bytes
|
size = len(tx.serialize_with_witness()) + add_bytes
|
||||||
pay_fee = round(fee_rate * size / 1000)
|
pay_fee = round(fee_rate * size / 1000)
|
||||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
self._log.info(
|
||||||
|
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
|
||||||
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
rv = self.rpc_wallet('gettransaction', [txid_hex])
|
rv = self.rpc_wallet("gettransaction", [txid_hex])
|
||||||
except Exception as ex:
|
except Exception as e: # noqa: F841
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug(
|
||||||
|
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
|
||||||
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
block_height = self.getBlockHeader(rv["blockhash"])["height"]
|
||||||
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def unlockWallet(self, password: str):
|
def unlockWallet(self, password: str):
|
||||||
super().unlockWallet(password)
|
super().unlockWallet(password)
|
||||||
|
if self._wallet_v20_compatible:
|
||||||
# Store password for initialiseWallet
|
# Store password for initialiseWallet
|
||||||
self._wallet_passphrase = password
|
self._wallet_passphrase = password
|
||||||
if not self._have_checked_seed:
|
if not self._have_checked_seed:
|
||||||
|
try:
|
||||||
self._sc.checkWalletSeed(self.coin_type())
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
except Exception as ex:
|
||||||
|
# dumphdinfo can fail if the wallet is not initialised
|
||||||
|
self._log.debug(f"DASH checkWalletSeed failed: {ex}.")
|
||||||
|
|
||||||
def lockWallet(self):
|
def lockWallet(self):
|
||||||
super().lockWallet()
|
super().lockWallet()
|
||||||
self._wallet_passphrase = ''
|
self._wallet_passphrase = ""
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
from .dcr import DCRInterface
|
from .dcr import DCRInterface
|
||||||
|
|
||||||
__all__ = ['DCRInterface',]
|
__all__ = [
|
||||||
|
"DCRInterface",
|
||||||
|
]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class SigHashType(IntEnum):
|
|||||||
SigHashSingle = 0x3
|
SigHashSingle = 0x3
|
||||||
SigHashAnyOneCanPay = 0x80
|
SigHashAnyOneCanPay = 0x80
|
||||||
|
|
||||||
SigHashMask = 0x1f
|
SigHashMask = 0x1F
|
||||||
|
|
||||||
|
|
||||||
class SignatureType(IntEnum):
|
class SignatureType(IntEnum):
|
||||||
@@ -33,7 +33,7 @@ class SignatureType(IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
class COutPoint:
|
class COutPoint:
|
||||||
__slots__ = ('hash', 'n', 'tree')
|
__slots__ = ("hash", "n", "tree")
|
||||||
|
|
||||||
def __init__(self, hash=0, n=0, tree=0):
|
def __init__(self, hash=0, n=0, tree=0):
|
||||||
self.hash = hash
|
self.hash = hash
|
||||||
@@ -41,24 +41,30 @@ class COutPoint:
|
|||||||
self.tree = tree
|
self.tree = tree
|
||||||
|
|
||||||
def get_hash(self) -> bytes:
|
def get_hash(self) -> bytes:
|
||||||
return self.hash.to_bytes(32, 'big')
|
return self.hash.to_bytes(32, "big")
|
||||||
|
|
||||||
|
|
||||||
class CTxIn:
|
class CTxIn:
|
||||||
__slots__ = ('prevout', 'sequence',
|
__slots__ = (
|
||||||
'value_in', 'block_height', 'block_index', 'signature_script') # Witness
|
"prevout",
|
||||||
|
"sequence",
|
||||||
|
"value_in",
|
||||||
|
"block_height",
|
||||||
|
"block_index",
|
||||||
|
"signature_script",
|
||||||
|
) # Witness
|
||||||
|
|
||||||
def __init__(self, prevout=COutPoint(), sequence=0):
|
def __init__(self, prevout=COutPoint(), sequence=0):
|
||||||
self.prevout = prevout
|
self.prevout = prevout
|
||||||
self.sequence = sequence
|
self.sequence = sequence
|
||||||
self.value_in = -1
|
self.value_in = -1
|
||||||
self.block_height = 0
|
self.block_height = 0
|
||||||
self.block_index = 0xffffffff
|
self.block_index = 0xFFFFFFFF
|
||||||
self.signature_script = bytes()
|
self.signature_script = bytes()
|
||||||
|
|
||||||
|
|
||||||
class CTxOut:
|
class CTxOut:
|
||||||
__slots__ = ('value', 'version', 'script_pubkey')
|
__slots__ = ("value", "version", "script_pubkey")
|
||||||
|
|
||||||
def __init__(self, value=0, script_pubkey=bytes()):
|
def __init__(self, value=0, script_pubkey=bytes()):
|
||||||
self.value = value
|
self.value = value
|
||||||
@@ -67,7 +73,7 @@ class CTxOut:
|
|||||||
|
|
||||||
|
|
||||||
class CTransaction:
|
class CTransaction:
|
||||||
__slots__ = ('hash', 'version', 'vin', 'vout', 'locktime', 'expiry')
|
__slots__ = ("hash", "version", "vin", "vout", "locktime", "expiry")
|
||||||
|
|
||||||
def __init__(self, tx=None):
|
def __init__(self, tx=None):
|
||||||
if tx is None:
|
if tx is None:
|
||||||
@@ -85,8 +91,8 @@ class CTransaction:
|
|||||||
|
|
||||||
def deserialize(self, data: bytes) -> None:
|
def deserialize(self, data: bytes) -> None:
|
||||||
|
|
||||||
version = int.from_bytes(data[:4], 'little')
|
version = int.from_bytes(data[:4], "little")
|
||||||
self.version = version & 0xffff
|
self.version = version & 0xFFFF
|
||||||
ser_type: int = version >> 16
|
ser_type: int = version >> 16
|
||||||
o = 4
|
o = 4
|
||||||
|
|
||||||
@@ -97,13 +103,13 @@ class CTransaction:
|
|||||||
for i in range(num_txin):
|
for i in range(num_txin):
|
||||||
txi = CTxIn()
|
txi = CTxIn()
|
||||||
txi.prevout = COutPoint()
|
txi.prevout = COutPoint()
|
||||||
txi.prevout.hash = int.from_bytes(data[o:o + 32], 'little')
|
txi.prevout.hash = int.from_bytes(data[o : o + 32], "little")
|
||||||
o += 32
|
o += 32
|
||||||
txi.prevout.n = int.from_bytes(data[o:o + 4], 'little')
|
txi.prevout.n = int.from_bytes(data[o : o + 4], "little")
|
||||||
o += 4
|
o += 4
|
||||||
txi.prevout.tree = data[o]
|
txi.prevout.tree = data[o]
|
||||||
o += 1
|
o += 1
|
||||||
txi.sequence = int.from_bytes(data[o:o + 4], 'little')
|
txi.sequence = int.from_bytes(data[o : o + 4], "little")
|
||||||
o += 4
|
o += 4
|
||||||
self.vin.append(txi)
|
self.vin.append(txi)
|
||||||
|
|
||||||
@@ -112,9 +118,9 @@ class CTransaction:
|
|||||||
|
|
||||||
for i in range(num_txout):
|
for i in range(num_txout):
|
||||||
txo = CTxOut()
|
txo = CTxOut()
|
||||||
txo.value = int.from_bytes(data[o:o + 8], 'little')
|
txo.value = int.from_bytes(data[o : o + 8], "little")
|
||||||
o += 8
|
o += 8
|
||||||
txo.version = int.from_bytes(data[o:o + 2], 'little')
|
txo.version = int.from_bytes(data[o : o + 2], "little")
|
||||||
o += 2
|
o += 2
|
||||||
script_bytes, nb = decode_compactsize(data, o)
|
script_bytes, nb = decode_compactsize(data, o)
|
||||||
o += nb
|
o += nb
|
||||||
@@ -122,9 +128,9 @@ class CTransaction:
|
|||||||
o += script_bytes
|
o += script_bytes
|
||||||
self.vout.append(txo)
|
self.vout.append(txo)
|
||||||
|
|
||||||
self.locktime = int.from_bytes(data[o:o + 4], 'little')
|
self.locktime = int.from_bytes(data[o : o + 4], "little")
|
||||||
o += 4
|
o += 4
|
||||||
self.expiry = int.from_bytes(data[o:o + 4], 'little')
|
self.expiry = int.from_bytes(data[o : o + 4], "little")
|
||||||
o += 4
|
o += 4
|
||||||
|
|
||||||
if ser_type == TxSerializeType.NoWitness:
|
if ser_type == TxSerializeType.NoWitness:
|
||||||
@@ -137,15 +143,15 @@ class CTransaction:
|
|||||||
self.vin = [CTxIn() for _ in range(num_wit_scripts)]
|
self.vin = [CTxIn() for _ in range(num_wit_scripts)]
|
||||||
else:
|
else:
|
||||||
if num_wit_scripts != len(self.vin):
|
if num_wit_scripts != len(self.vin):
|
||||||
raise ValueError('non equal witness and prefix txin quantities')
|
raise ValueError("non equal witness and prefix txin quantities")
|
||||||
|
|
||||||
for i in range(num_wit_scripts):
|
for i in range(num_wit_scripts):
|
||||||
txi = self.vin[i]
|
txi = self.vin[i]
|
||||||
txi.value_in = int.from_bytes(data[o:o + 8], 'little')
|
txi.value_in = int.from_bytes(data[o : o + 8], "little")
|
||||||
o += 8
|
o += 8
|
||||||
txi.block_height = int.from_bytes(data[o:o + 4], 'little')
|
txi.block_height = int.from_bytes(data[o : o + 4], "little")
|
||||||
o += 4
|
o += 4
|
||||||
txi.block_index = int.from_bytes(data[o:o + 4], 'little')
|
txi.block_index = int.from_bytes(data[o : o + 4], "little")
|
||||||
o += 4
|
o += 4
|
||||||
script_bytes, nb = decode_compactsize(data, o)
|
script_bytes, nb = decode_compactsize(data, o)
|
||||||
o += nb
|
o += nb
|
||||||
@@ -154,34 +160,36 @@ class CTransaction:
|
|||||||
|
|
||||||
def serialize(self, ser_type=TxSerializeType.Full) -> bytes:
|
def serialize(self, ser_type=TxSerializeType.Full) -> bytes:
|
||||||
data = bytes()
|
data = bytes()
|
||||||
version = (self.version & 0xffff) | (ser_type << 16)
|
version = (self.version & 0xFFFF) | (ser_type << 16)
|
||||||
data += version.to_bytes(4, 'little')
|
data += version.to_bytes(4, "little")
|
||||||
|
|
||||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
|
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
|
||||||
data += encode_compactsize(len(self.vin))
|
data += encode_compactsize(len(self.vin))
|
||||||
for txi in self.vin:
|
for txi in self.vin:
|
||||||
data += txi.prevout.hash.to_bytes(32, 'little')
|
data += txi.prevout.hash.to_bytes(32, "little")
|
||||||
data += txi.prevout.n.to_bytes(4, 'little')
|
data += txi.prevout.n.to_bytes(4, "little")
|
||||||
data += txi.prevout.tree.to_bytes(1, 'little')
|
data += txi.prevout.tree.to_bytes(1, "little")
|
||||||
data += txi.sequence.to_bytes(4, 'little')
|
data += txi.sequence.to_bytes(4, "little")
|
||||||
|
|
||||||
data += encode_compactsize(len(self.vout))
|
data += encode_compactsize(len(self.vout))
|
||||||
for txo in self.vout:
|
for txo in self.vout:
|
||||||
data += txo.value.to_bytes(8, 'little')
|
data += txo.value.to_bytes(8, "little")
|
||||||
data += txo.version.to_bytes(2, 'little')
|
data += txo.version.to_bytes(2, "little")
|
||||||
data += encode_compactsize(len(txo.script_pubkey))
|
data += encode_compactsize(len(txo.script_pubkey))
|
||||||
data += txo.script_pubkey
|
data += txo.script_pubkey
|
||||||
|
|
||||||
data += self.locktime.to_bytes(4, 'little')
|
data += self.locktime.to_bytes(4, "little")
|
||||||
data += self.expiry.to_bytes(4, 'little')
|
data += self.expiry.to_bytes(4, "little")
|
||||||
|
|
||||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness:
|
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness:
|
||||||
data += encode_compactsize(len(self.vin))
|
data += encode_compactsize(len(self.vin))
|
||||||
for txi in self.vin:
|
for txi in self.vin:
|
||||||
tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values
|
tc_value_in = (
|
||||||
data += tc_value_in.to_bytes(8, 'little')
|
txi.value_in & 0xFFFFFFFFFFFFFFFF
|
||||||
data += txi.block_height.to_bytes(4, 'little')
|
) # Convert negative values
|
||||||
data += txi.block_index.to_bytes(4, 'little')
|
data += tc_value_in.to_bytes(8, "little")
|
||||||
|
data += txi.block_height.to_bytes(4, "little")
|
||||||
|
data += txi.block_index.to_bytes(4, "little")
|
||||||
data += encode_compactsize(len(txi.signature_script))
|
data += encode_compactsize(len(txi.signature_script))
|
||||||
data += txi.signature_script
|
data += txi.signature_script
|
||||||
|
|
||||||
@@ -191,10 +199,10 @@ class CTransaction:
|
|||||||
return blake256(self.serialize(TxSerializeType.NoWitness))[::-1]
|
return blake256(self.serialize(TxSerializeType.NoWitness))[::-1]
|
||||||
|
|
||||||
def TxHashWitness(self) -> bytes:
|
def TxHashWitness(self) -> bytes:
|
||||||
raise ValueError('todo')
|
raise ValueError("todo")
|
||||||
|
|
||||||
def TxHashFull(self) -> bytes:
|
def TxHashFull(self) -> bytes:
|
||||||
raise ValueError('todo')
|
raise ValueError("todo")
|
||||||
|
|
||||||
|
|
||||||
def findOutput(tx, script_pk: bytes):
|
def findOutput(tx, script_pk: bytes):
|
||||||
|
|||||||
@@ -9,34 +9,34 @@ import traceback
|
|||||||
from basicswap.rpc import Jsonrpc
|
from basicswap.rpc import Jsonrpc
|
||||||
|
|
||||||
|
|
||||||
def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'):
|
def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1"):
|
||||||
try:
|
try:
|
||||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
|
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
||||||
x = Jsonrpc(url)
|
x = Jsonrpc(url)
|
||||||
x.__handler = None
|
x.__handler = None
|
||||||
v = x.json_request(method, params)
|
v = x.json_request(method, params)
|
||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode("utf-8"))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method)
|
raise ValueError("RPC server error " + str(ex) + ", method: " + method)
|
||||||
|
|
||||||
if 'error' in r and r['error'] is not None:
|
if "error" in r and r["error"] is not None:
|
||||||
raise ValueError('RPC error ' + str(r['error']))
|
raise ValueError("RPC error " + str(r["error"]))
|
||||||
|
|
||||||
return r['result']
|
return r["result"]
|
||||||
|
|
||||||
|
|
||||||
def openrpc(rpc_port, auth, host='127.0.0.1'):
|
def openrpc(rpc_port, auth, host="127.0.0.1"):
|
||||||
try:
|
try:
|
||||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
|
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
||||||
return Jsonrpc(url)
|
return Jsonrpc(url)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise ValueError('RPC error ' + str(ex))
|
raise ValueError("RPC error " + str(ex))
|
||||||
|
|
||||||
|
|
||||||
def make_rpc_func(port, auth, host='127.0.0.1'):
|
def make_rpc_func(port, auth, host="127.0.0.1"):
|
||||||
port = port
|
port = port
|
||||||
auth = auth
|
auth = auth
|
||||||
host = host
|
host = host
|
||||||
@@ -44,4 +44,5 @@ def make_rpc_func(port, auth, host='127.0.0.1'):
|
|||||||
def rpc_func(method, params=None):
|
def rpc_func(method, params=None):
|
||||||
nonlocal port, auth, host
|
nonlocal port, auth, host
|
||||||
return callrpc(port, auth, method, params, host)
|
return callrpc(port, auth, method, params, host)
|
||||||
|
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
OP_0 = 0x00
|
OP_0 = 0x00
|
||||||
OP_DATA_1 = 0x01
|
OP_DATA_1 = 0x01
|
||||||
OP_1NEGATE = 0x4f
|
OP_1NEGATE = 0x4F
|
||||||
OP_1 = 0x51
|
OP_1 = 0x51
|
||||||
OP_IF = 0x63
|
OP_IF = 0x63
|
||||||
OP_ELSE = 0x67
|
OP_ELSE = 0x67
|
||||||
@@ -16,13 +16,13 @@ OP_DROP = 0x75
|
|||||||
OP_DUP = 0x76
|
OP_DUP = 0x76
|
||||||
OP_EQUAL = 0x87
|
OP_EQUAL = 0x87
|
||||||
OP_EQUALVERIFY = 0x88
|
OP_EQUALVERIFY = 0x88
|
||||||
OP_PUSHDATA1 = 0x4c
|
OP_PUSHDATA1 = 0x4C
|
||||||
OP_PUSHDATA2 = 0x4d
|
OP_PUSHDATA2 = 0x4D
|
||||||
OP_PUSHDATA4 = 0x4e
|
OP_PUSHDATA4 = 0x4E
|
||||||
OP_HASH160 = 0xa9
|
OP_HASH160 = 0xA9
|
||||||
OP_CHECKSIG = 0xac
|
OP_CHECKSIG = 0xAC
|
||||||
OP_CHECKMULTISIG = 0xae
|
OP_CHECKMULTISIG = 0xAE
|
||||||
OP_CHECKSEQUENCEVERIFY = 0xb2
|
OP_CHECKSEQUENCEVERIFY = 0xB2
|
||||||
|
|
||||||
|
|
||||||
def push_script_data(data_array: bytearray, data: bytes) -> None:
|
def push_script_data(data_array: bytearray, data: bytes) -> None:
|
||||||
@@ -39,12 +39,12 @@ def push_script_data(data_array: bytearray, data: bytes) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if len_data < OP_PUSHDATA1:
|
if len_data < OP_PUSHDATA1:
|
||||||
data_array += len_data.to_bytes(1, 'little')
|
data_array += len_data.to_bytes(1, "little")
|
||||||
elif len_data <= 0xff:
|
elif len_data <= 0xFF:
|
||||||
data_array += bytes((OP_PUSHDATA1, len_data))
|
data_array += bytes((OP_PUSHDATA1, len_data))
|
||||||
elif len_data <= 0xffff:
|
elif len_data <= 0xFFFF:
|
||||||
data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, 'little')
|
data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, "little")
|
||||||
else:
|
else:
|
||||||
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
|
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, "little")
|
||||||
|
|
||||||
data_array += data
|
data_array += data
|
||||||
|
|||||||
@@ -10,38 +10,56 @@ import subprocess
|
|||||||
|
|
||||||
|
|
||||||
def createDCRWallet(args, hex_seed, logging, delay_event):
|
def createDCRWallet(args, hex_seed, logging, delay_event):
|
||||||
logging.info('Creating DCR wallet')
|
logging.info("Creating DCR wallet")
|
||||||
|
|
||||||
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
||||||
|
|
||||||
|
if os.name == "nt":
|
||||||
|
str_args = " ".join(args)
|
||||||
|
p = subprocess.Popen(
|
||||||
|
str_args, shell=True, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w
|
||||||
|
)
|
||||||
|
else:
|
||||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w)
|
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w)
|
||||||
|
|
||||||
try:
|
def readOutput():
|
||||||
while p.poll() is None:
|
buf = os.read(pipe_r, 1024).decode("utf-8")
|
||||||
while len(select.select([pipe_r], [], [], 0)[0]) == 1:
|
|
||||||
buf = os.read(pipe_r, 1024).decode('utf-8')
|
|
||||||
logging.debug(f'dcrwallet {buf}')
|
|
||||||
response = None
|
response = None
|
||||||
if 'Use the existing configured private passphrase' in buf:
|
if "Opened wallet" in buf:
|
||||||
response = b'y\n'
|
|
||||||
elif 'Do you want to add an additional layer of encryption' in buf:
|
|
||||||
response = b'n\n'
|
|
||||||
elif 'Do you have an existing wallet seed' in buf:
|
|
||||||
response = b'y\n'
|
|
||||||
elif 'Enter existing wallet seed' in buf:
|
|
||||||
response = (hex_seed + '\n').encode('utf-8')
|
|
||||||
elif 'Seed input successful' in buf:
|
|
||||||
pass
|
pass
|
||||||
elif 'Upgrading database from version' in buf:
|
elif "Use the existing configured private passphrase" in buf:
|
||||||
|
response = b"y\n"
|
||||||
|
elif "Do you want to add an additional layer of encryption" in buf:
|
||||||
|
response = b"n\n"
|
||||||
|
elif "Do you have an existing wallet seed" in buf:
|
||||||
|
response = b"y\n"
|
||||||
|
elif "Enter existing wallet seed" in buf:
|
||||||
|
response = (hex_seed + "\n").encode("utf-8")
|
||||||
|
elif "Seed input successful" in buf:
|
||||||
pass
|
pass
|
||||||
elif 'Ticket commitments db upgrade done' in buf:
|
elif "Upgrading database from version" in buf:
|
||||||
|
pass
|
||||||
|
elif "Ticket commitments db upgrade done" in buf:
|
||||||
|
pass
|
||||||
|
elif "The wallet has been created successfully" in buf:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Unexpected output: {buf}')
|
raise ValueError(f"Unexpected output: {buf}")
|
||||||
if response is not None:
|
if response is not None:
|
||||||
p.stdin.write(response)
|
p.stdin.write(response)
|
||||||
p.stdin.flush()
|
p.stdin.flush()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while p.poll() is None:
|
||||||
|
if os.name == "nt":
|
||||||
|
readOutput()
|
||||||
|
delay_event.wait(0.1)
|
||||||
|
continue
|
||||||
|
while len(select.select([pipe_r], [], [], 0)[0]) == 1:
|
||||||
|
readOutput()
|
||||||
delay_event.wait(0.1)
|
delay_event.wait(0.1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'dcrwallet --create failed: {e}')
|
logging.error(f"dcrwallet --create failed: {e}")
|
||||||
finally:
|
finally:
|
||||||
if p.poll() is None:
|
if p.poll() is None:
|
||||||
p.terminate()
|
p.terminate()
|
||||||
|
|||||||
62
basicswap/interface/doge.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 The BasicSwap developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
from .btc import BTCInterface
|
||||||
|
from basicswap.chainparams import Coins
|
||||||
|
from basicswap.util.crypto import hash160
|
||||||
|
|
||||||
|
from basicswap.contrib.test_framework.script import (
|
||||||
|
CScript,
|
||||||
|
OP_DUP,
|
||||||
|
OP_CHECKSIG,
|
||||||
|
OP_HASH160,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DOGEInterface(BTCInterface):
|
||||||
|
@staticmethod
|
||||||
|
def coin_type():
|
||||||
|
return Coins.DOGE
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
return 192
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
|
return 192
|
||||||
|
|
||||||
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
|
super(DOGEInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
|
|
||||||
|
def getScriptDest(self, script: bytearray) -> bytearray:
|
||||||
|
# P2SH
|
||||||
|
|
||||||
|
script_hash = hash160(script)
|
||||||
|
assert len(script_hash) == 20
|
||||||
|
|
||||||
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
|
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
|
# Return P2PKH
|
||||||
|
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
||||||
|
|
||||||
|
def encodeScriptDest(self, script_dest: bytes) -> str:
|
||||||
|
# Extract hash from script
|
||||||
|
script_hash = script_dest[2:-1]
|
||||||
|
return self.sh_to_address(script_hash)
|
||||||
|
|
||||||
|
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||||
|
add_bytes = 107
|
||||||
|
size = len(tx.serialize_with_witness()) + add_bytes
|
||||||
|
pay_fee = round(fee_rate * size / 1000)
|
||||||
|
self._log.info(
|
||||||
|
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
|
||||||
|
)
|
||||||
|
return pay_fee
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -40,10 +41,15 @@ class FIROInterface(BTCInterface):
|
|||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
|
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
# No multiwallet support
|
# No multiwallet support
|
||||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
self.rpc_wallet = make_rpc_func(
|
||||||
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
|
)
|
||||||
|
|
||||||
def getExchangeName(self, exchange_name):
|
if "wallet_name" in coin_settings:
|
||||||
return 'zcoin'
|
raise ValueError(f"Invalid setting for {self.coin_name()}: wallet_name")
|
||||||
|
|
||||||
|
def getExchangeName(self, exchange_name: str) -> str:
|
||||||
|
return "zcoin"
|
||||||
|
|
||||||
def initialiseWallet(self, key):
|
def initialiseWallet(self, key):
|
||||||
# load with -hdseed= parameter
|
# load with -hdseed= parameter
|
||||||
@@ -52,8 +58,8 @@ class FIROInterface(BTCInterface):
|
|||||||
def checkWallets(self) -> int:
|
def checkWallets(self) -> int:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def getNewAddress(self, use_segwit, label='swap_receive'):
|
def getNewAddress(self, use_segwit, label="swap_receive"):
|
||||||
return self.rpc('getnewaddress', [label])
|
return self.rpc("getnewaddress", [label])
|
||||||
# addr_plain = self.rpc('getnewaddress', [label])
|
# addr_plain = self.rpc('getnewaddress', [label])
|
||||||
# return self.rpc('addwitnessaddress', [addr_plain])
|
# return self.rpc('addwitnessaddress', [addr_plain])
|
||||||
|
|
||||||
@@ -61,20 +67,20 @@ class FIROInterface(BTCInterface):
|
|||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
|
|
||||||
def encodeSegwitAddress(self, script):
|
def encodeSegwitAddress(self, script):
|
||||||
raise ValueError('TODO')
|
raise ValueError("TODO")
|
||||||
|
|
||||||
def decodeSegwitAddress(self, addr):
|
def decodeSegwitAddress(self, addr):
|
||||||
raise ValueError('TODO')
|
raise ValueError("TODO")
|
||||||
|
|
||||||
def isWatchOnlyAddress(self, address):
|
def isWatchOnlyAddress(self, address):
|
||||||
addr_info = self.rpc('validateaddress', [address])
|
addr_info = self.rpc("validateaddress", [address])
|
||||||
return addr_info['iswatchonly']
|
return addr_info["iswatchonly"]
|
||||||
|
|
||||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||||
addr_info = self.rpc('validateaddress', [address])
|
addr_info = self.rpc("validateaddress", [address])
|
||||||
if not or_watch_only:
|
if not or_watch_only:
|
||||||
return addr_info['ismine']
|
return addr_info["ismine"]
|
||||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
return addr_info["ismine"] or addr_info["iswatchonly"]
|
||||||
|
|
||||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||||
lock_tx_dest = self.getScriptDest(lock_script)
|
lock_tx_dest = self.getScriptDest(lock_script)
|
||||||
@@ -82,58 +88,85 @@ class FIROInterface(BTCInterface):
|
|||||||
|
|
||||||
if not self.isAddressMine(address, or_watch_only=True):
|
if not self.isAddressMine(address, or_watch_only=True):
|
||||||
# Expects P2WSH nested in BIP16_P2SH
|
# Expects P2WSH nested in BIP16_P2SH
|
||||||
ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
self.rpc("importaddress", [lock_tx_dest.hex(), "bid lock", False, True])
|
||||||
addr_info = self.rpc('validateaddress', [address])
|
|
||||||
|
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
def getLockTxHeight(
|
||||||
|
self,
|
||||||
|
txid,
|
||||||
|
dest_address,
|
||||||
|
bid_amount,
|
||||||
|
rescan_from,
|
||||||
|
find_index: bool = False,
|
||||||
|
vout: int = -1,
|
||||||
|
):
|
||||||
# Add watchonly address and rescan if required
|
# Add watchonly address and rescan if required
|
||||||
|
|
||||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||||
self.importWatchOnlyAddress(dest_address, 'bid')
|
self.importWatchOnlyAddress(dest_address, "bid")
|
||||||
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
self._log.info(
|
||||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
"Imported watch-only addr: {}".format(self._log.addr(dest_address))
|
||||||
|
)
|
||||||
|
self._log.info(
|
||||||
|
"Rescanning {} chain from height: {}".format(
|
||||||
|
self.coin_name(), rescan_from
|
||||||
|
)
|
||||||
|
)
|
||||||
self.rescanBlockchainForAddress(rescan_from, dest_address)
|
self.rescanBlockchainForAddress(rescan_from, dest_address)
|
||||||
|
|
||||||
return_txid = True if txid is None else False
|
return_txid = True if txid is None else False
|
||||||
if txid is None:
|
if txid is None:
|
||||||
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
|
txns = self.rpc(
|
||||||
|
"listunspent",
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
9999999,
|
||||||
|
[
|
||||||
|
dest_address,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
for tx in txns:
|
for tx in txns:
|
||||||
if self.make_int(tx['amount']) == bid_amount:
|
if self.make_int(tx["amount"]) == bid_amount:
|
||||||
txid = bytes.fromhex(tx['txid'])
|
txid = bytes.fromhex(tx["txid"])
|
||||||
break
|
break
|
||||||
|
|
||||||
if txid is None:
|
if txid is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tx = self.rpc('gettransaction', [txid.hex()])
|
tx = self.rpc("gettransaction", [txid.hex()])
|
||||||
|
|
||||||
block_height = 0
|
block_height = 0
|
||||||
if 'blockhash' in tx:
|
if "blockhash" in tx:
|
||||||
block_header = self.rpc('getblockheader', [tx['blockhash']])
|
block_header = self.rpc("getblockheader", [tx["blockhash"]])
|
||||||
block_height = block_header['height']
|
block_height = block_header["height"]
|
||||||
|
|
||||||
rv = {
|
rv = {
|
||||||
'depth': 0 if 'confirmations' not in tx else tx['confirmations'],
|
"depth": 0 if "confirmations" not in tx else tx["confirmations"],
|
||||||
'height': block_height}
|
"height": block_height,
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e))
|
self._log.debug(
|
||||||
|
"getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e)
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if find_index:
|
if find_index:
|
||||||
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
|
tx_obj = self.rpc("decoderawtransaction", [tx["hex"]])
|
||||||
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||||
|
|
||||||
if return_txid:
|
if return_txid:
|
||||||
rv['txid'] = txid.hex()
|
rv["txid"] = txid.hex()
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
|
def createSCLockTx(
|
||||||
|
self, value: int, script: bytearray, vkbv: bytes = None
|
||||||
|
) -> bytes:
|
||||||
tx = CTransaction()
|
tx = CTransaction()
|
||||||
tx.nVersion = self.txVersion()
|
tx.nVersion = self.txVersion()
|
||||||
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
||||||
@@ -144,24 +177,36 @@ class FIROInterface(BTCInterface):
|
|||||||
return self.fundTx(tx_bytes, feerate)
|
return self.fundTx(tx_bytes, feerate)
|
||||||
|
|
||||||
def signTxWithWallet(self, tx):
|
def signTxWithWallet(self, tx):
|
||||||
rv = self.rpc('signrawtransaction', [tx.hex()])
|
rv = self.rpc("signrawtransaction", [tx.hex()])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv["hex"])
|
||||||
|
|
||||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
def createRawFundedTransaction(
|
||||||
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
self,
|
||||||
|
addr_to: str,
|
||||||
|
amount: int,
|
||||||
|
sub_fee: bool = False,
|
||||||
|
lock_unspents: bool = True,
|
||||||
|
) -> str:
|
||||||
|
txn = self.rpc(
|
||||||
|
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
||||||
|
)
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
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}')
|
self._log.debug(
|
||||||
|
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
||||||
|
)
|
||||||
options = {
|
options = {
|
||||||
'lockUnspents': lock_unspents,
|
"lockUnspents": lock_unspents,
|
||||||
'feeRate': fee_rate,
|
"feeRate": fee_rate,
|
||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options['subtractFeeFromOutputs'] = [0,]
|
options["subtractFeeFromOutputs"] = [
|
||||||
return self.rpc('fundrawtransaction', [txn, options])['hex']
|
0,
|
||||||
|
]
|
||||||
|
return self.rpc("fundrawtransaction", [txn, options])["hex"]
|
||||||
|
|
||||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||||
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
return self.rpc("signrawtransaction", [txn_funded])["hex"]
|
||||||
|
|
||||||
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||||
# Return P2PKH
|
# Return P2PKH
|
||||||
@@ -188,60 +233,75 @@ class FIROInterface(BTCInterface):
|
|||||||
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee]
|
params = [addr_to, value, "", "", subfee]
|
||||||
return self.rpc('sendtoaddress', params)
|
return self.rpc("sendtoaddress", params)
|
||||||
|
|
||||||
def getWalletSeedID(self):
|
def getWalletSeedID(self):
|
||||||
return self.rpc('getwalletinfo')['hdmasterkeyid']
|
return self.rpc("getwalletinfo")["hdmasterkeyid"]
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
return self.make_int(self.rpc("getwalletinfo")["balance"])
|
||||||
|
|
||||||
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||||
add_bytes = 107
|
add_bytes = 107
|
||||||
size = len(tx.serialize_with_witness()) + add_bytes
|
size = len(tx.serialize_with_witness()) + add_bytes
|
||||||
pay_fee = round(fee_rate * size / 1000)
|
pay_fee = round(fee_rate * size / 1000)
|
||||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
self._log.info(
|
||||||
|
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
|
||||||
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
rv = self.rpc(
|
||||||
return bytes.fromhex(rv['hex'])
|
"signrawtransaction",
|
||||||
|
[
|
||||||
|
tx.hex(),
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
key_wif,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return bytes.fromhex(rv["hex"])
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
rv = self.rpc('gettransaction', [txid_hex])
|
rv = self.rpc("gettransaction", [txid_hex])
|
||||||
except Exception as ex:
|
except Exception as e: # noqa: F841
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug(
|
||||||
|
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
|
||||||
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
block_height = self.getBlockHeader(rv["blockhash"])["height"]
|
||||||
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getProofOfFunds(self, amount_for, extra_commit_bytes):
|
def getProofOfFunds(self, amount_for, extra_commit_bytes):
|
||||||
# TODO: Lock unspent and use same output/s to fund bid
|
# TODO: Lock unspent and use same output/s to fund bid
|
||||||
|
|
||||||
unspents_by_addr = dict()
|
unspents_by_addr = dict()
|
||||||
unspents = self.rpc('listunspent')
|
unspents = self.rpc("listunspent")
|
||||||
for u in unspents:
|
for u in unspents:
|
||||||
if u['spendable'] is not True:
|
if u["spendable"] is not True:
|
||||||
continue
|
continue
|
||||||
if u['address'] not in unspents_by_addr:
|
if u["address"] not in unspents_by_addr:
|
||||||
unspents_by_addr[u['address']] = {'total': 0, 'utxos': []}
|
unspents_by_addr[u["address"]] = {"total": 0, "utxos": []}
|
||||||
utxo_amount: int = self.make_int(u['amount'], r=1)
|
utxo_amount: int = self.make_int(u["amount"], r=1)
|
||||||
unspents_by_addr[u['address']]['total'] += utxo_amount
|
unspents_by_addr[u["address"]]["total"] += utxo_amount
|
||||||
unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout']))
|
unspents_by_addr[u["address"]]["utxos"].append(
|
||||||
|
(utxo_amount, u["txid"], u["vout"])
|
||||||
|
)
|
||||||
|
|
||||||
max_utxos: int = 4
|
max_utxos: int = 4
|
||||||
|
|
||||||
viable_addrs = []
|
viable_addrs = []
|
||||||
for addr, data in unspents_by_addr.items():
|
for addr, data in unspents_by_addr.items():
|
||||||
if data['total'] >= amount_for:
|
if data["total"] >= amount_for:
|
||||||
# Sort from largest to smallest amount
|
# Sort from largest to smallest amount
|
||||||
sorted_utxos = sorted(data['utxos'], key=lambda x: x[0])
|
sorted_utxos = sorted(data["utxos"], key=lambda x: x[0])
|
||||||
|
|
||||||
# Max outputs required to reach amount_for
|
# Max outputs required to reach amount_for
|
||||||
utxos_req: int = 0
|
utxos_req: int = 0
|
||||||
@@ -256,13 +316,17 @@ class FIROInterface(BTCInterface):
|
|||||||
viable_addrs.append(addr)
|
viable_addrs.append(addr)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof')
|
ensure(
|
||||||
|
len(viable_addrs) > 0, "Could not find address with enough funds for proof"
|
||||||
|
)
|
||||||
|
|
||||||
sign_for_addr: str = random.choice(viable_addrs)
|
sign_for_addr: str = random.choice(viable_addrs)
|
||||||
self._log.debug('sign_for_addr %s', sign_for_addr)
|
self._log.debug("sign_for_addr %s", sign_for_addr)
|
||||||
|
|
||||||
prove_utxos = []
|
prove_utxos = []
|
||||||
sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0])
|
sorted_utxos = sorted(
|
||||||
|
unspents_by_addr[sign_for_addr]["utxos"], key=lambda x: x[0]
|
||||||
|
)
|
||||||
|
|
||||||
hasher = hashlib.sha256()
|
hasher = hashlib.sha256()
|
||||||
|
|
||||||
@@ -272,18 +336,29 @@ class FIROInterface(BTCInterface):
|
|||||||
outpoint = (bytes.fromhex(utxo[1]), utxo[2])
|
outpoint = (bytes.fromhex(utxo[1]), utxo[2])
|
||||||
prove_utxos.append(outpoint)
|
prove_utxos.append(outpoint)
|
||||||
hasher.update(outpoint[0])
|
hasher.update(outpoint[0])
|
||||||
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
hasher.update(outpoint[1].to_bytes(2, "big"))
|
||||||
if sum_value >= amount_for:
|
if sum_value >= amount_for:
|
||||||
break
|
break
|
||||||
utxos_hash = hasher.digest()
|
utxos_hash = hasher.digest()
|
||||||
|
|
||||||
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo
|
if (
|
||||||
|
self.using_segwit()
|
||||||
|
): # TODO: Use isSegwitAddress when scantxoutset can use combo
|
||||||
# 'Address does not refer to key' for non p2pkh
|
# 'Address does not refer to key' for non p2pkh
|
||||||
pkh = self.decodeAddress(sign_for_addr)
|
pkh = self.decodeAddress(sign_for_addr)
|
||||||
sign_for_addr = self.pkh_to_address(pkh)
|
sign_for_addr = self.pkh_to_address(pkh)
|
||||||
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
self._log.debug("sign_for_addr converted %s", sign_for_addr)
|
||||||
|
|
||||||
signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
signature = self.rpc(
|
||||||
|
"signmessage",
|
||||||
|
[
|
||||||
|
sign_for_addr,
|
||||||
|
sign_for_addr
|
||||||
|
+ "_swap_proof_"
|
||||||
|
+ utxos_hash.hex()
|
||||||
|
+ extra_commit_bytes.hex(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
return (sign_for_addr, signature, prove_utxos)
|
return (sign_for_addr, signature, prove_utxos)
|
||||||
|
|
||||||
@@ -292,19 +367,23 @@ class FIROInterface(BTCInterface):
|
|||||||
sum_value: int = 0
|
sum_value: int = 0
|
||||||
for outpoint in utxos:
|
for outpoint in utxos:
|
||||||
hasher.update(outpoint[0])
|
hasher.update(outpoint[0])
|
||||||
hasher.update(outpoint[1].to_bytes(2, 'big'))
|
hasher.update(outpoint[1].to_bytes(2, "big"))
|
||||||
utxos_hash = hasher.digest()
|
utxos_hash = hasher.digest()
|
||||||
|
|
||||||
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature)
|
passed = self.verifyMessage(
|
||||||
ensure(passed is True, 'Proof of funds signature invalid')
|
address,
|
||||||
|
address + "_swap_proof_" + utxos_hash.hex() + extra_commit_bytes.hex(),
|
||||||
|
signature,
|
||||||
|
)
|
||||||
|
ensure(passed is True, "Proof of funds signature invalid")
|
||||||
|
|
||||||
if self.using_segwit():
|
if self.using_segwit():
|
||||||
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
|
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
|
||||||
|
|
||||||
sum_value: int = 0
|
sum_value: int = 0
|
||||||
for outpoint in utxos:
|
for outpoint in utxos:
|
||||||
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
|
txout = self.rpc("gettxout", [outpoint[0].hex(), outpoint[1]])
|
||||||
sum_value += self.make_int(txout['value'])
|
sum_value += self.make_int(txout["value"])
|
||||||
|
|
||||||
return sum_value
|
return sum_value
|
||||||
|
|
||||||
@@ -314,15 +393,15 @@ class FIROInterface(BTCInterface):
|
|||||||
chain_blocks: int = self.getChainHeight()
|
chain_blocks: int = self.getChainHeight()
|
||||||
|
|
||||||
current_height: int = chain_blocks
|
current_height: int = chain_blocks
|
||||||
block_hash = self.rpc('getblockhash', [current_height])
|
block_hash = self.rpc("getblockhash", [current_height])
|
||||||
|
|
||||||
script_hash: bytes = self.decodeAddress(addr_find)
|
script_hash: bytes = self.decodeAddress(addr_find)
|
||||||
find_scriptPubKey = self.getDestForScriptHash(script_hash)
|
find_scriptPubKey = self.getDestForScriptHash(script_hash)
|
||||||
|
|
||||||
while current_height > height_start:
|
while current_height > height_start:
|
||||||
block_hash = self.rpc('getblockhash', [current_height])
|
block_hash = self.rpc("getblockhash", [current_height])
|
||||||
|
|
||||||
block = self.rpc('getblock', [block_hash, False])
|
block = self.rpc("getblock", [block_hash, False])
|
||||||
decoded_block = CBlock()
|
decoded_block = CBlock()
|
||||||
decoded_block = FromHex(decoded_block, block)
|
decoded_block = FromHex(decoded_block, block)
|
||||||
for tx in decoded_block.vtx:
|
for tx in decoded_block.vtx:
|
||||||
@@ -330,38 +409,46 @@ class FIROInterface(BTCInterface):
|
|||||||
if txo.scriptPubKey == find_scriptPubKey:
|
if txo.scriptPubKey == find_scriptPubKey:
|
||||||
tx.rehash()
|
tx.rehash()
|
||||||
txid = i2b(tx.sha256)
|
txid = i2b(tx.sha256)
|
||||||
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
|
self._log.info(
|
||||||
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
|
"Found output to addr: {} in tx {} in block {}".format(
|
||||||
self.rpc('invalidateblock', [block_hash])
|
addr_find, txid.hex(), block_hash
|
||||||
self.rpc('reconsiderblock', [block_hash])
|
)
|
||||||
|
)
|
||||||
|
self._log.info(
|
||||||
|
"rescanblockchain hack invalidateblock {}".format(
|
||||||
|
block_hash
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.rpc("invalidateblock", [block_hash])
|
||||||
|
self.rpc("reconsiderblock", [block_hash])
|
||||||
return
|
return
|
||||||
current_height -= 1
|
current_height -= 1
|
||||||
|
|
||||||
def getBlockWithTxns(self, block_hash: str):
|
def getBlockWithTxns(self, block_hash: str):
|
||||||
# TODO: Bypass decoderawtransaction and getblockheader
|
# TODO: Bypass decoderawtransaction and getblockheader
|
||||||
block = self.rpc('getblock', [block_hash, False])
|
block = self.rpc("getblock", [block_hash, False])
|
||||||
block_header = self.rpc('getblockheader', [block_hash])
|
block_header = self.rpc("getblockheader", [block_hash])
|
||||||
decoded_block = CBlock()
|
decoded_block = CBlock()
|
||||||
decoded_block = FromHex(decoded_block, block)
|
decoded_block = FromHex(decoded_block, block)
|
||||||
|
|
||||||
tx_rv = []
|
tx_rv = []
|
||||||
for tx in decoded_block.vtx:
|
for tx in decoded_block.vtx:
|
||||||
tx_hex = tx.serialize_with_witness().hex()
|
tx_hex = tx.serialize_with_witness().hex()
|
||||||
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
|
tx_dec = self.rpc("decoderawtransaction", [tx_hex])
|
||||||
if 'hex' not in tx_dec:
|
if "hex" not in tx_dec:
|
||||||
tx_dec['hex'] = tx_hex
|
tx_dec["hex"] = tx_hex
|
||||||
|
|
||||||
tx_rv.append(tx_dec)
|
tx_rv.append(tx_dec)
|
||||||
|
|
||||||
block_rv = {
|
block_rv = {
|
||||||
'hash': block_hash,
|
"hash": block_hash,
|
||||||
'previousblockhash': block_header['previousblockhash'],
|
"previousblockhash": block_header["previousblockhash"],
|
||||||
'tx': tx_rv,
|
"tx": tx_rv,
|
||||||
'confirmations': block_header['confirmations'],
|
"confirmations": block_header["confirmations"],
|
||||||
'height': block_header['height'],
|
"height": block_header["height"],
|
||||||
'time': block_header['time'],
|
"time": block_header["time"],
|
||||||
'version': block_header['version'],
|
"version": block_header["version"],
|
||||||
'merkleroot': block_header['merkleroot'],
|
"merkleroot": block_header["merkleroot"],
|
||||||
}
|
}
|
||||||
|
|
||||||
return block_rv
|
return block_rv
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2023 tecnovert
|
||||||
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -17,75 +18,86 @@ class LTCInterface(BTCInterface):
|
|||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
self._rpc_wallet_mweb = 'mweb'
|
self._rpc_wallet_mweb = coin_settings.get("mweb_wallet_name", "mweb")
|
||||||
self.rpc_wallet_mweb = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet_mweb)
|
self.rpc_wallet_mweb = make_rpc_func(
|
||||||
|
self._rpcport,
|
||||||
|
self._rpcauth,
|
||||||
|
host=self._rpc_host,
|
||||||
|
wallet=self._rpc_wallet_mweb,
|
||||||
|
)
|
||||||
|
|
||||||
def getNewMwebAddress(self, use_segwit=False, label='swap_receive') -> str:
|
def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str:
|
||||||
return self.rpc_wallet_mweb('getnewaddress', [label, 'mweb'])
|
return self.rpc_wallet_mweb("getnewaddress", [label, "mweb"])
|
||||||
|
|
||||||
def getNewStealthAddress(self, label=''):
|
def getNewStealthAddress(self, label=""):
|
||||||
return self.getNewMwebAddress(False, label)
|
return self.getNewMwebAddress(False, label)
|
||||||
|
|
||||||
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
|
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
|
||||||
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
params = [addr_to, value, "", "", subfee, True, self._conf_target]
|
||||||
if type_from == 'mweb':
|
if type_from == "mweb":
|
||||||
return self.rpc_wallet_mweb('sendtoaddress', params)
|
return self.rpc_wallet_mweb("sendtoaddress", params)
|
||||||
return self.rpc_wallet('sendtoaddress', params)
|
return self.rpc_wallet("sendtoaddress", params)
|
||||||
|
|
||||||
def createUTXO(self, value_sats: int):
|
def createUTXO(self, value_sats: int):
|
||||||
# Create a new address and send value_sats to it
|
# Create a new address and send value_sats to it
|
||||||
|
|
||||||
spendable_balance = self.getSpendableBalance()
|
spendable_balance = self.getSpendableBalance()
|
||||||
if spendable_balance < value_sats:
|
if spendable_balance < value_sats:
|
||||||
raise ValueError('Balance too low')
|
raise ValueError("Balance too low")
|
||||||
|
|
||||||
address = self.getNewAddress(self._use_segwit, 'create_utxo')
|
address = self.getNewAddress(self._use_segwit, "create_utxo")
|
||||||
return self.withdrawCoin(self.format_amount(value_sats), 'plain', address, False), address
|
return (
|
||||||
|
self.withdrawCoin(self.format_amount(value_sats), "plain", address, False),
|
||||||
|
address,
|
||||||
|
)
|
||||||
|
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
rv = super(LTCInterface, self).getWalletInfo()
|
rv = super(LTCInterface, self).getWalletInfo()
|
||||||
|
mweb_info = self.rpc_wallet_mweb("getwalletinfo")
|
||||||
mweb_info = self.rpc_wallet_mweb('getwalletinfo')
|
rv["mweb_balance"] = mweb_info["balance"]
|
||||||
rv['mweb_balance'] = mweb_info['balance']
|
rv["mweb_unconfirmed"] = mweb_info["unconfirmed_balance"]
|
||||||
rv['mweb_unconfirmed'] = mweb_info['unconfirmed_balance']
|
rv["mweb_immature"] = mweb_info["immature_balance"]
|
||||||
rv['mweb_immature'] = mweb_info['immature_balance']
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def getUnspentsByAddr(self):
|
def getUnspentsByAddr(self):
|
||||||
unspent_addr = dict()
|
unspent_addr = dict()
|
||||||
unspent = self.rpc_wallet('listunspent')
|
unspent = self.rpc_wallet("listunspent")
|
||||||
for u in unspent:
|
for u in unspent:
|
||||||
if u.get('spendable', False) is False:
|
if u.get("spendable", False) is False:
|
||||||
continue
|
continue
|
||||||
if u.get('solvable', False) is False: # Filter out mweb outputs
|
if u.get("solvable", False) is False: # Filter out mweb outputs
|
||||||
continue
|
continue
|
||||||
if 'address' not in u:
|
if "address" not in u:
|
||||||
continue
|
continue
|
||||||
if 'desc' in u:
|
if "desc" in u:
|
||||||
desc = u['desc']
|
desc = u["desc"]
|
||||||
if self.using_segwit:
|
if self.using_segwit:
|
||||||
if self.use_p2shp2wsh():
|
if self.use_p2shp2wsh():
|
||||||
if not desc.startswith('sh(wpkh'):
|
if not desc.startswith("sh(wpkh"):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if not desc.startswith('wpkh'):
|
if not desc.startswith("wpkh"):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if not desc.startswith('pkh'):
|
if not desc.startswith("pkh"):
|
||||||
continue
|
continue
|
||||||
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
|
unspent_addr[u["address"]] = unspent_addr.get(
|
||||||
|
u["address"], 0
|
||||||
|
) + self.make_int(u["amount"], r=1)
|
||||||
return unspent_addr
|
return unspent_addr
|
||||||
|
|
||||||
|
|
||||||
class LTCInterfaceMWEB(LTCInterface):
|
class LTCInterfaceMWEB(LTCInterface):
|
||||||
@staticmethod
|
|
||||||
def coin_type():
|
def interface_type(self) -> int:
|
||||||
return Coins.LTC_MWEB
|
return Coins.LTC_MWEB
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
||||||
self._rpc_wallet = 'mweb'
|
self._rpc_wallet = coin_settings.get("mweb_wallet_name", "mweb")
|
||||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
|
self.rpc_wallet = make_rpc_func(
|
||||||
|
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
|
||||||
|
)
|
||||||
|
|
||||||
def chainparams(self):
|
def chainparams(self):
|
||||||
return chainparams[Coins.LTC]
|
return chainparams[Coins.LTC]
|
||||||
@@ -95,56 +107,54 @@ class LTCInterfaceMWEB(LTCInterface):
|
|||||||
|
|
||||||
def coin_name(self) -> str:
|
def coin_name(self) -> str:
|
||||||
coin_chainparams = chainparams[Coins.LTC]
|
coin_chainparams = chainparams[Coins.LTC]
|
||||||
if coin_chainparams.get('use_ticker_as_name', False):
|
return coin_chainparams["name"].capitalize() + " MWEB"
|
||||||
return coin_chainparams['ticker'] + ' MWEB'
|
|
||||||
return coin_chainparams['name'].capitalize() + ' MWEB'
|
|
||||||
|
|
||||||
def ticker(self) -> str:
|
def ticker(self) -> str:
|
||||||
ticker = chainparams[Coins.LTC]['ticker']
|
ticker = chainparams[Coins.LTC]["ticker"]
|
||||||
if self._network == 'testnet':
|
if self._network == "testnet":
|
||||||
ticker = 't' + ticker
|
ticker = "t" + ticker
|
||||||
elif self._network == 'regtest':
|
elif self._network == "regtest":
|
||||||
ticker = 'rt' + ticker
|
ticker = "rt" + ticker
|
||||||
return ticker + '_MWEB'
|
return ticker + "_MWEB"
|
||||||
|
|
||||||
def getNewAddress(self, use_segwit=False, label='swap_receive') -> str:
|
def getNewAddress(self, use_segwit=False, label="swap_receive") -> str:
|
||||||
return self.getNewMwebAddress()
|
return self.getNewMwebAddress()
|
||||||
|
|
||||||
def has_mweb_wallet(self) -> bool:
|
def has_mweb_wallet(self) -> bool:
|
||||||
return 'mweb' in self.rpc('listwallets')
|
return "mweb" in self.rpc("listwallets")
|
||||||
|
|
||||||
def init_wallet(self, password=None):
|
def init_wallet(self, password=None):
|
||||||
# If system is encrypted mweb wallet will be created at first unlock
|
# If system is encrypted mweb wallet will be created at first unlock
|
||||||
|
|
||||||
self._log.info('init_wallet - {}'.format(self.ticker()))
|
self._log.info("init_wallet - {}".format(self.ticker()))
|
||||||
|
|
||||||
self._log.info('Creating mweb wallet for {}.'.format(self.coin_name()))
|
self._log.info(f"Creating wallet {self._rpc_wallet} for {self.coin_name()}.")
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
||||||
self.rpc('createwallet', ['mweb', False, True, password, False, False, True])
|
self.rpc("createwallet", ["mweb", False, True, password, False, False, True])
|
||||||
|
|
||||||
if password is not None:
|
if password is not None:
|
||||||
# Max timeout value, ~3 years
|
# Max timeout value, ~3 years
|
||||||
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
self.rpc_wallet("walletpassphrase", [password, 100000000])
|
||||||
|
|
||||||
if self.getWalletSeedID() == 'Not found':
|
if self.getWalletSeedID() == "Not found":
|
||||||
self._sc.initialiseWallet(self.coin_type())
|
self._sc.initialiseWallet(self.interface_type())
|
||||||
|
|
||||||
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
|
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
|
||||||
self.rpc('unloadwallet', ['mweb'])
|
self.rpc("unloadwallet", ["mweb"])
|
||||||
self.rpc('loadwallet', ['mweb'])
|
self.rpc("loadwallet", ["mweb"])
|
||||||
if password is not None:
|
if password is not None:
|
||||||
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
self.rpc_wallet("walletpassphrase", [password, 100000000])
|
||||||
self.rpc_wallet('keypoolrefill')
|
self.rpc_wallet("keypoolrefill")
|
||||||
|
|
||||||
def unlockWallet(self, password: str):
|
def unlockWallet(self, password: str):
|
||||||
if password == '':
|
if password == "":
|
||||||
return
|
return
|
||||||
self._log.info('unlockWallet - {}'.format(self.ticker()))
|
self._log.info("unlockWallet - {}".format(self.ticker()))
|
||||||
|
|
||||||
if not self.has_mweb_wallet():
|
if not self.has_mweb_wallet():
|
||||||
self.init_wallet(password)
|
self.init_wallet(password)
|
||||||
else:
|
else:
|
||||||
# Max timeout value, ~3 years
|
# Max timeout value, ~3 years
|
||||||
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
self.rpc_wallet("walletpassphrase", [password, 100000000])
|
||||||
|
|
||||||
self._sc.checkWalletSeed(self.coin_type())
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
|||||||
@@ -14,26 +14,38 @@ class NMCInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.NMC
|
return Coins.NMC
|
||||||
|
|
||||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
def getLockTxHeight(
|
||||||
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
self,
|
||||||
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
|
txid,
|
||||||
self._log.debug('[rm] scantxoutset end')
|
dest_address,
|
||||||
|
bid_amount,
|
||||||
|
rescan_from,
|
||||||
|
find_index: bool = False,
|
||||||
|
vout: int = -1,
|
||||||
|
):
|
||||||
|
self._log.debug("[rm] scantxoutset start") # scantxoutset is slow
|
||||||
|
ro = self.rpc(
|
||||||
|
"scantxoutset", ["start", ["addr({})".format(dest_address)]]
|
||||||
|
) # TODO: Use combo(address) where possible
|
||||||
|
self._log.debug("[rm] scantxoutset end")
|
||||||
return_txid = True if txid is None else False
|
return_txid = True if txid is None else False
|
||||||
for o in ro['unspents']:
|
for o in ro["unspents"]:
|
||||||
if txid and o['txid'] != txid.hex():
|
if txid and o["txid"] != txid.hex():
|
||||||
continue
|
continue
|
||||||
# Verify amount
|
# Verify amount
|
||||||
if self.make_int(o['amount']) != int(bid_amount):
|
if self.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'])
|
self._log.warning(
|
||||||
|
"Found output to lock tx address of incorrect value: %s, %s",
|
||||||
|
str(o["amount"]),
|
||||||
|
o["txid"],
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rv = {
|
rv = {"depth": 0, "height": o["height"]}
|
||||||
'depth': 0,
|
if o["height"] > 0:
|
||||||
'height': o['height']}
|
rv["depth"] = ro["height"] - o["height"]
|
||||||
if o['height'] > 0:
|
|
||||||
rv['depth'] = ro['height'] - o['height']
|
|
||||||
if find_index:
|
if find_index:
|
||||||
rv['index'] = o['vout']
|
rv["index"] = o["vout"]
|
||||||
if return_txid:
|
if return_txid:
|
||||||
rv['txid'] = o['txid']
|
rv["txid"] = o["txid"]
|
||||||
return rv
|
return rv
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
from .btc import BTCInterface
|
from .btc import BTCInterface
|
||||||
from basicswap.contrib.test_framework.messages import (
|
from basicswap.contrib.test_framework.messages import CTxOut
|
||||||
CTxOut)
|
|
||||||
|
|
||||||
|
|
||||||
class PassthroughBTCInterface(BTCInterface):
|
class PassthroughBTCInterface(BTCInterface):
|
||||||
@@ -15,5 +14,5 @@ class PassthroughBTCInterface(BTCInterface):
|
|||||||
super().__init__(coin_settings, network)
|
super().__init__(coin_settings, network)
|
||||||
self.txoType = CTxOut
|
self.txoType = CTxOut
|
||||||
self._network = network
|
self._network = network
|
||||||
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"])
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022 tecnovert
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# 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,11 +12,7 @@ from .btc import BTCInterface
|
|||||||
from basicswap.rpc import make_rpc_func
|
from basicswap.rpc import make_rpc_func
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.util.address import decodeAddress
|
from basicswap.util.address import decodeAddress
|
||||||
from .contrib.pivx_test_framework.messages import (
|
from .contrib.pivx_test_framework.messages import CBlock, ToHex, FromHex, CTransaction
|
||||||
CBlock,
|
|
||||||
ToHex,
|
|
||||||
FromHex,
|
|
||||||
CTransaction)
|
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import (
|
||||||
CScript,
|
CScript,
|
||||||
OP_DUP,
|
OP_DUP,
|
||||||
@@ -33,65 +30,79 @@ class PIVXInterface(BTCInterface):
|
|||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
|
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
# No multiwallet support
|
# No multiwallet support
|
||||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
self.rpc_wallet = make_rpc_func(
|
||||||
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
|
)
|
||||||
|
|
||||||
def checkWallets(self) -> int:
|
def checkWallets(self) -> int:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def signTxWithWallet(self, tx):
|
def signTxWithWallet(self, tx):
|
||||||
rv = self.rpc('signrawtransaction', [tx.hex()])
|
rv = self.rpc("signrawtransaction", [tx.hex()])
|
||||||
return bytes.fromhex(rv['hex'])
|
return bytes.fromhex(rv["hex"])
|
||||||
|
|
||||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
def createRawFundedTransaction(
|
||||||
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
self,
|
||||||
|
addr_to: str,
|
||||||
|
amount: int,
|
||||||
|
sub_fee: bool = False,
|
||||||
|
lock_unspents: bool = True,
|
||||||
|
) -> str:
|
||||||
|
txn = self.rpc(
|
||||||
|
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
||||||
|
)
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
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}')
|
self._log.debug(
|
||||||
|
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
||||||
|
)
|
||||||
options = {
|
options = {
|
||||||
'lockUnspents': lock_unspents,
|
"lockUnspents": lock_unspents,
|
||||||
'feeRate': fee_rate,
|
"feeRate": fee_rate,
|
||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options['subtractFeeFromOutputs'] = [0,]
|
options["subtractFeeFromOutputs"] = [
|
||||||
return self.rpc('fundrawtransaction', [txn, options])['hex']
|
0,
|
||||||
|
]
|
||||||
|
return self.rpc("fundrawtransaction", [txn, options])["hex"]
|
||||||
|
|
||||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||||
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
return self.rpc("signrawtransaction", [txn_funded])["hex"]
|
||||||
|
|
||||||
def decodeAddress(self, address):
|
def decodeAddress(self, address):
|
||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
|
|
||||||
def getBlockWithTxns(self, block_hash):
|
def getBlockWithTxns(self, block_hash):
|
||||||
# TODO: Bypass decoderawtransaction and getblockheader
|
# TODO: Bypass decoderawtransaction and getblockheader
|
||||||
block = self.rpc('getblock', [block_hash, False])
|
block = self.rpc("getblock", [block_hash, False])
|
||||||
block_header = self.rpc('getblockheader', [block_hash])
|
block_header = self.rpc("getblockheader", [block_hash])
|
||||||
decoded_block = CBlock()
|
decoded_block = CBlock()
|
||||||
decoded_block = FromHex(decoded_block, block)
|
decoded_block = FromHex(decoded_block, block)
|
||||||
|
|
||||||
tx_rv = []
|
tx_rv = []
|
||||||
for tx in decoded_block.vtx:
|
for tx in decoded_block.vtx:
|
||||||
tx_dec = self.rpc('decoderawtransaction', [ToHex(tx)])
|
tx_dec = self.rpc("decoderawtransaction", [ToHex(tx)])
|
||||||
tx_rv.append(tx_dec)
|
tx_rv.append(tx_dec)
|
||||||
|
|
||||||
block_rv = {
|
block_rv = {
|
||||||
'hash': block_hash,
|
"hash": block_hash,
|
||||||
'previousblockhash': block_header['previousblockhash'],
|
"previousblockhash": block_header["previousblockhash"],
|
||||||
'tx': tx_rv,
|
"tx": tx_rv,
|
||||||
'confirmations': block_header['confirmations'],
|
"confirmations": block_header["confirmations"],
|
||||||
'height': block_header['height'],
|
"height": block_header["height"],
|
||||||
'time': block_header['time'],
|
"time": block_header["time"],
|
||||||
'version': block_header['version'],
|
"version": block_header["version"],
|
||||||
'merkleroot': block_header['merkleroot'],
|
"merkleroot": block_header["merkleroot"],
|
||||||
}
|
}
|
||||||
|
|
||||||
return block_rv
|
return block_rv
|
||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, '', '', subfee]
|
params = [addr_to, value, "", "", subfee]
|
||||||
return self.rpc('sendtoaddress', params)
|
return self.rpc("sendtoaddress", params)
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
return self.make_int(self.rpc("getwalletinfo")["balance"])
|
||||||
|
|
||||||
def loadTx(self, tx_bytes):
|
def loadTx(self, tx_bytes):
|
||||||
# Load tx from bytes to internal representation
|
# Load tx from bytes to internal representation
|
||||||
@@ -107,22 +118,35 @@ class PIVXInterface(BTCInterface):
|
|||||||
add_bytes = 107
|
add_bytes = 107
|
||||||
size = len(tx.serialize_with_witness()) + add_bytes
|
size = len(tx.serialize_with_witness()) + add_bytes
|
||||||
pay_fee = round(fee_rate * size / 1000)
|
pay_fee = round(fee_rate * size / 1000)
|
||||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
self._log.info(
|
||||||
|
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
|
||||||
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
rv = self.rpc(
|
||||||
return bytes.fromhex(rv['hex'])
|
"signrawtransaction",
|
||||||
|
[
|
||||||
|
tx.hex(),
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
key_wif,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return bytes.fromhex(rv["hex"])
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
rv = self.rpc('gettransaction', [txid_hex])
|
rv = self.rpc("gettransaction", [txid_hex])
|
||||||
except Exception as ex:
|
except Exception as e: # noqa: F841
|
||||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
self._log.debug(
|
||||||
|
"findTxnByHash getrawtransaction failed: {}".format(txid_hex)
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed:
|
||||||
block_height = self.getBlockHeader(rv['blockhash'])['height']
|
block_height = self.getBlockHeader(rv["blockhash"])["height"]
|
||||||
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
||||||
return None
|
return None
|
||||||
|
|||||||
32
basicswap/interface/wow.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
from basicswap.chainparams import WOW_COIN, Coins
|
||||||
|
from .xmr import XMRInterface
|
||||||
|
|
||||||
|
|
||||||
|
class WOWInterface(XMRInterface):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def coin_type():
|
||||||
|
return Coins.WOW
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ticker_str() -> int:
|
||||||
|
return Coins.WOW.name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def COIN():
|
||||||
|
return WOW_COIN
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exp() -> int:
|
||||||
|
return 11
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def depth_spendable() -> int:
|
||||||
|
return 3
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
# 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 json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import basicswap.contrib.ed25519_fast as edf
|
import basicswap.contrib.ed25519_fast as edf
|
||||||
@@ -27,16 +27,9 @@ from coincurve.dleag import (
|
|||||||
from basicswap.interface.base import (
|
from basicswap.interface.base import (
|
||||||
Curves,
|
Curves,
|
||||||
)
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import i2b, b2i, b2h, dumpj, ensure, TemporaryError
|
||||||
i2b, b2i, b2h,
|
from basicswap.util.network import is_private_ip_address
|
||||||
dumpj,
|
from basicswap.rpc_xmr import make_xmr_rpc_func, make_xmr_rpc2_func
|
||||||
ensure,
|
|
||||||
TemporaryError)
|
|
||||||
from basicswap.util.network import (
|
|
||||||
is_private_ip_address)
|
|
||||||
from basicswap.rpc_xmr import (
|
|
||||||
make_xmr_rpc_func,
|
|
||||||
make_xmr_rpc2_func)
|
|
||||||
from basicswap.chainparams import XMR_COIN, Coins
|
from basicswap.chainparams import XMR_COIN, Coins
|
||||||
from basicswap.interface.base import CoinInterface
|
from basicswap.interface.base import CoinInterface
|
||||||
|
|
||||||
@@ -50,6 +43,10 @@ class XMRInterface(CoinInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.XMR
|
return Coins.XMR
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ticker_str() -> int:
|
||||||
|
return Coins.XMR.name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def COIN():
|
def COIN():
|
||||||
return XMR_COIN
|
return XMR_COIN
|
||||||
@@ -72,95 +69,167 @@ class XMRInterface(CoinInterface):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
raise ValueError('Not possible')
|
raise ValueError("Not possible")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
# TODO: Estimate with ringsize
|
||||||
|
return 1604
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
# TODO: Estimate with ringsize
|
# TODO: Estimate with ringsize
|
||||||
return 1604
|
return 1604
|
||||||
|
|
||||||
|
def is_transient_error(self, ex) -> bool:
|
||||||
|
str_error: str = str(ex).lower()
|
||||||
|
if "failed to get earliest fork height" in str_error:
|
||||||
|
return True
|
||||||
|
return super().is_transient_error(ex)
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(network)
|
super().__init__(network)
|
||||||
|
|
||||||
self._addr_prefix = self.chainparams_network()['address_prefix']
|
self._addr_prefix = self.chainparams_network()["address_prefix"]
|
||||||
|
|
||||||
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)
|
||||||
self.setFeePriority(coin_settings.get('fee_priority', 0))
|
self.setFeePriority(coin_settings.get("fee_priority", 0))
|
||||||
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
|
||||||
self._wallet_password = None
|
self._wallet_password = None
|
||||||
self._have_checked_seed = False
|
self._have_checked_seed = False
|
||||||
|
self._wallet_filename = coin_settings.get("wallet_name", "swap_wallet")
|
||||||
|
|
||||||
daemon_login = None
|
daemon_login = None
|
||||||
if coin_settings.get('rpcuser', '') != '':
|
if coin_settings.get("rpcuser", "") != "":
|
||||||
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
|
daemon_login = (
|
||||||
|
coin_settings.get("rpcuser", ""),
|
||||||
|
coin_settings.get("rpcpassword", ""),
|
||||||
|
)
|
||||||
|
|
||||||
rpchost = coin_settings.get('rpchost', '127.0.0.1')
|
rpchost = coin_settings.get("rpchost", "127.0.0.1")
|
||||||
proxy_host = None
|
proxy_host = None
|
||||||
proxy_port = None
|
proxy_port = None
|
||||||
# Connect to the daemon over a proxy if not running locally
|
# Connect to the daemon over a proxy if not running locally
|
||||||
if swap_client:
|
if swap_client:
|
||||||
chain_client_settings = swap_client.getChainClientSettings(self.coin_type())
|
chain_client_settings = swap_client.getChainClientSettings(self.coin_type())
|
||||||
manage_daemon: bool = chain_client_settings['manage_daemon']
|
manage_daemon: bool = chain_client_settings["manage_daemon"]
|
||||||
if swap_client.use_tor_proxy:
|
if swap_client.use_tor_proxy:
|
||||||
if manage_daemon is False:
|
if manage_daemon is False:
|
||||||
log_str: str = ''
|
log_str: str = ""
|
||||||
have_cc_tor_opt = 'use_tor' in chain_client_settings
|
have_cc_tor_opt = "use_tor" in chain_client_settings
|
||||||
if have_cc_tor_opt and chain_client_settings['use_tor'] is False:
|
if have_cc_tor_opt and chain_client_settings["use_tor"] is False:
|
||||||
log_str = ' bypassing proxy (use_tor false for XMR)'
|
log_str = (
|
||||||
|
f" bypassing proxy (use_tor false for {self.coin_name()})"
|
||||||
|
)
|
||||||
elif have_cc_tor_opt is False and is_private_ip_address(rpchost):
|
elif have_cc_tor_opt is False and is_private_ip_address(rpchost):
|
||||||
log_str = ' bypassing proxy (private ip address)'
|
log_str = " bypassing proxy (private ip address)"
|
||||||
else:
|
else:
|
||||||
proxy_host = swap_client.tor_proxy_host
|
proxy_host = swap_client.tor_proxy_host
|
||||||
proxy_port = swap_client.tor_proxy_port
|
proxy_port = swap_client.tor_proxy_port
|
||||||
log_str = f' through proxy at {proxy_host}'
|
log_str = f" through proxy at {proxy_host}"
|
||||||
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}.')
|
self._log.info(
|
||||||
|
f"Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.')
|
self._log.info(
|
||||||
|
f"Not connecting to local {self.coin_name()} daemon through proxy."
|
||||||
|
)
|
||||||
elif manage_daemon is False:
|
elif manage_daemon is False:
|
||||||
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}.')
|
self._log.info(
|
||||||
|
f"Connecting to remote {self.coin_name()} daemon at {rpchost}."
|
||||||
|
)
|
||||||
|
|
||||||
self._rpctimeout = coin_settings.get('rpctimeout', 60)
|
self._rpctimeout = coin_settings.get("rpctimeout", 60)
|
||||||
self._walletrpctimeout = coin_settings.get('walletrpctimeout', 120)
|
self._walletrpctimeout = coin_settings.get("walletrpctimeout", 120)
|
||||||
self._walletrpctimeoutlong = coin_settings.get('walletrpctimeoutlong', 600)
|
# walletrpctimeoutlong likely unneeded
|
||||||
|
self._walletrpctimeoutlong = coin_settings.get("walletrpctimeoutlong", 600)
|
||||||
|
|
||||||
self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node(j) ')
|
self.rpc = make_xmr_rpc_func(
|
||||||
self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint
|
coin_settings["rpcport"],
|
||||||
self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ')
|
daemon_login,
|
||||||
|
host=rpchost,
|
||||||
|
proxy_host=proxy_host,
|
||||||
|
proxy_port=proxy_port,
|
||||||
|
default_timeout=self._rpctimeout,
|
||||||
|
tag="Node(j) ",
|
||||||
|
)
|
||||||
|
self.rpc2 = make_xmr_rpc2_func(
|
||||||
|
coin_settings["rpcport"],
|
||||||
|
daemon_login,
|
||||||
|
host=rpchost,
|
||||||
|
proxy_host=proxy_host,
|
||||||
|
proxy_port=proxy_port,
|
||||||
|
default_timeout=self._rpctimeout,
|
||||||
|
tag="Node ",
|
||||||
|
) # non-json endpoint
|
||||||
|
self.rpc_wallet = make_xmr_rpc_func(
|
||||||
|
coin_settings["walletrpcport"],
|
||||||
|
coin_settings["walletrpcauth"],
|
||||||
|
host=coin_settings.get("walletrpchost", "127.0.0.1"),
|
||||||
|
default_timeout=self._walletrpctimeout,
|
||||||
|
tag="Wallet ",
|
||||||
|
)
|
||||||
|
|
||||||
def setFeePriority(self, new_priority):
|
def setFeePriority(self, new_priority):
|
||||||
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
|
ensure(new_priority >= 0 and new_priority < 4, "Invalid fee_priority value")
|
||||||
self._fee_priority = new_priority
|
self._fee_priority = new_priority
|
||||||
|
|
||||||
def setWalletFilename(self, wallet_filename):
|
|
||||||
self._wallet_filename = wallet_filename
|
|
||||||
|
|
||||||
def createWallet(self, params):
|
def createWallet(self, params):
|
||||||
if self._wallet_password is not None:
|
if self._wallet_password is not None:
|
||||||
params['password'] = self._wallet_password
|
params["password"] = self._wallet_password
|
||||||
rv = self.rpc_wallet('generate_from_keys', params)
|
rv = self.rpc_wallet("generate_from_keys", params)
|
||||||
self._log.info('generate_from_keys %s', dumpj(rv))
|
if "address" in rv:
|
||||||
|
new_address: str = rv["address"]
|
||||||
|
is_watch_only: bool = "Watch-only" in rv.get("info", "")
|
||||||
|
self._log.info(
|
||||||
|
"Generated{} {} wallet: {}".format(
|
||||||
|
" watch-only" if is_watch_only else "",
|
||||||
|
self.coin_name(),
|
||||||
|
self._log.addr(new_address),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._log.debug("generate_from_keys %s", dumpj(rv))
|
||||||
|
raise ValueError("generate_from_keys failed")
|
||||||
|
|
||||||
def openWallet(self, filename):
|
def openWallet(self, filename):
|
||||||
params = {'filename': filename}
|
params = {"filename": filename}
|
||||||
if self._wallet_password is not None:
|
if self._wallet_password is not None:
|
||||||
params['password'] = self._wallet_password
|
params["password"] = self._wallet_password
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Can't reopen the same wallet in windows, !is_keys_file_locked()
|
self.rpc_wallet("open_wallet", params)
|
||||||
self.rpc_wallet('close_wallet')
|
# TODO Remove `refresh` after upstream fix to refresh on open_wallet
|
||||||
except Exception:
|
self.rpc_wallet("refresh")
|
||||||
pass
|
except Exception as e:
|
||||||
self.rpc_wallet('open_wallet', params)
|
if "no connection to daemon" in str(e):
|
||||||
|
self._log.debug(f"{self.coin_name()} {e}")
|
||||||
|
return # bypass refresh error to allow startup with a busy daemon
|
||||||
|
|
||||||
def initialiseWallet(self, key_view: bytes, key_spend: bytes, restore_height=None) -> None:
|
try:
|
||||||
|
# TODO Remove `store` after upstream fix to autosave on close_wallet
|
||||||
|
self.rpc_wallet("store")
|
||||||
|
self.rpc_wallet("close_wallet")
|
||||||
|
self._log.debug(f"Attempt to save and close {self.coin_name()} wallet")
|
||||||
|
except Exception as e: # noqa: F841
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.rpc_wallet("open_wallet", params)
|
||||||
|
# TODO Remove `refresh` after upstream fix to refresh on open_wallet
|
||||||
|
self.rpc_wallet("refresh")
|
||||||
|
self._log.debug(f"Reattempt to open {self.coin_name()} wallet")
|
||||||
|
|
||||||
|
def initialiseWallet(
|
||||||
|
self, key_view: bytes, key_spend: bytes, restore_height=None
|
||||||
|
) -> None:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
try:
|
try:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
# TODO: Check address
|
# TODO: Check address
|
||||||
return # Wallet exists
|
return # Wallet exists
|
||||||
except Exception as e:
|
except Exception as e: # noqa: F841
|
||||||
pass
|
pass
|
||||||
|
|
||||||
Kbv = self.getPubkey(key_view)
|
Kbv = self.getPubkey(key_view)
|
||||||
@@ -168,11 +237,11 @@ class XMRInterface(CoinInterface):
|
|||||||
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'filename': self._wallet_filename,
|
"filename": self._wallet_filename,
|
||||||
'address': address_b58,
|
"address": address_b58,
|
||||||
'viewkey': b2h(key_view[::-1]),
|
"viewkey": b2h(key_view[::-1]),
|
||||||
'spendkey': b2h(key_spend[::-1]),
|
"spendkey": b2h(key_spend[::-1]),
|
||||||
'restore_height': self._restore_height,
|
"restore_height": self._restore_height,
|
||||||
}
|
}
|
||||||
self.createWallet(params)
|
self.createWallet(params)
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
@@ -182,86 +251,97 @@ class XMRInterface(CoinInterface):
|
|||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
|
|
||||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||||
self.rpc_wallet('get_languages')
|
self.rpc_wallet("get_languages")
|
||||||
|
|
||||||
def getDaemonVersion(self):
|
def getDaemonVersion(self):
|
||||||
return self.rpc_wallet('get_version')['version']
|
return self.rpc_wallet("get_version")["version"]
|
||||||
|
|
||||||
def getBlockchainInfo(self):
|
def getBlockchainInfo(self):
|
||||||
get_height = self.rpc2('get_height', timeout=self._rpctimeout)
|
get_height = self.rpc2("get_height", timeout=self._rpctimeout)
|
||||||
rv = {
|
rv = {
|
||||||
'blocks': get_height['height'],
|
"blocks": get_height["height"],
|
||||||
'verificationprogress': 0.0,
|
"verificationprogress": 0.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# get_block_count.block_count is how many blocks are in the longest chain known to the node.
|
# 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
|
||||||
if get_height['untrusted'] is True:
|
if get_height["untrusted"] is True:
|
||||||
rv['bootstrapping'] = True
|
rv["bootstrapping"] = True
|
||||||
get_info = self.rpc2('get_info', timeout=self._rpctimeout)
|
get_info = self.rpc2("get_info", timeout=self._rpctimeout)
|
||||||
if 'height_without_bootstrap' in get_info:
|
if "height_without_bootstrap" in get_info:
|
||||||
rv['blocks'] = get_info['height_without_bootstrap']
|
rv["blocks"] = get_info["height_without_bootstrap"]
|
||||||
|
|
||||||
rv['known_block_count'] = get_info['height']
|
rv["known_block_count"] = get_info["height"]
|
||||||
if rv['known_block_count'] > rv['blocks']:
|
if rv["known_block_count"] > rv["blocks"]:
|
||||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
rv["verificationprogress"] = rv["blocks"] / rv["known_block_count"]
|
||||||
else:
|
else:
|
||||||
rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count']
|
rv["known_block_count"] = self.rpc(
|
||||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
"get_block_count", timeout=self._rpctimeout
|
||||||
|
)["count"]
|
||||||
|
rv["verificationprogress"] = rv["blocks"] / rv["known_block_count"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.warning('XMR get_block_count failed with: %s', str(e))
|
self._log.warning(f"{self.ticker_str()} get_block_count failed with: {e}")
|
||||||
rv['verificationprogress'] = 0.0
|
rv["verificationprogress"] = 0.0
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def getChainHeight(self):
|
def getChainHeight(self):
|
||||||
return self.rpc2('get_height', timeout=self._rpctimeout)['height']
|
return self.rpc2("get_height", timeout=self._rpctimeout)["height"]
|
||||||
|
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
try:
|
try:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 'Failed to open wallet' in str(e):
|
if "Failed to open wallet" in str(e):
|
||||||
rv = {'encrypted': True, 'locked': True, 'balance': 0, 'unconfirmed_balance': 0}
|
rv = {
|
||||||
|
"encrypted": True,
|
||||||
|
"locked": True,
|
||||||
|
"balance": 0,
|
||||||
|
"unconfirmed_balance": 0,
|
||||||
|
}
|
||||||
return rv
|
return rv
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
rv = {}
|
rv = {}
|
||||||
self.rpc_wallet('refresh')
|
balance_info = self.rpc_wallet("get_balance")
|
||||||
balance_info = self.rpc_wallet('get_balance')
|
|
||||||
|
|
||||||
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
|
rv["wallet_blocks"] = self.rpc_wallet("get_height")["height"]
|
||||||
rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance'])
|
rv["balance"] = self.format_amount(balance_info["unlocked_balance"])
|
||||||
rv['encrypted'] = False if self._wallet_password is None else True
|
rv["unconfirmed_balance"] = self.format_amount(
|
||||||
rv['locked'] = False
|
balance_info["balance"] - balance_info["unlocked_balance"]
|
||||||
|
)
|
||||||
|
rv["encrypted"] = False if self._wallet_password is None else True
|
||||||
|
rv["locked"] = False
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def getMainWalletAddress(self) -> str:
|
def getMainWalletAddress(self) -> str:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
return self.rpc_wallet('get_address')['address']
|
return self.rpc_wallet("get_address")["address"]
|
||||||
|
|
||||||
def getNewAddress(self, placeholder) -> str:
|
def getNewAddress(self, placeholder) -> str:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
new_address = self.rpc_wallet('create_address', {'account_index': 0})['address']
|
new_address = self.rpc_wallet("create_address", {"account_index": 0})[
|
||||||
self.rpc_wallet('store')
|
"address"
|
||||||
|
]
|
||||||
|
self.rpc_wallet("store")
|
||||||
return new_address
|
return new_address
|
||||||
|
|
||||||
def get_fee_rate(self, conf_target: int = 2):
|
def get_fee_rate(self, conf_target: int = 2):
|
||||||
# fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest].
|
# fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest].
|
||||||
fee_est = self.rpc('get_fee_estimate')
|
fee_est = self.rpc("get_fee_estimate")
|
||||||
if conf_target <= 1:
|
if conf_target <= 1:
|
||||||
conf_target = 1 # normal
|
conf_target = 1 # normal
|
||||||
else:
|
else:
|
||||||
conf_target = 0 # slow
|
conf_target = 0 # slow
|
||||||
fee_per_k_bytes = fee_est['fees'][conf_target] * 1000
|
fee_per_k_bytes = fee_est["fees"][conf_target] * 1000
|
||||||
|
|
||||||
return float(self.format_amount(fee_per_k_bytes)), 'get_fee_estimate'
|
return float(self.format_amount(fee_per_k_bytes)), "get_fee_estimate"
|
||||||
|
|
||||||
def getNewSecretKey(self) -> bytes:
|
def getNewRandomKey(self) -> bytes:
|
||||||
# Note: Returned bytes are in big endian order
|
# Note: Returned bytes are in big endian order
|
||||||
return i2b(edu.get_secret())
|
return i2b(edu.get_secret())
|
||||||
|
|
||||||
@@ -290,7 +370,7 @@ class XMRInterface(CoinInterface):
|
|||||||
|
|
||||||
def verifyKey(self, k: int) -> bool:
|
def verifyKey(self, k: int) -> bool:
|
||||||
i = b2i(k)
|
i = b2i(k)
|
||||||
return (i < edf.l and i > 8)
|
return i < edf.l and i > 8
|
||||||
|
|
||||||
def verifyPubkey(self, pubkey_bytes):
|
def verifyPubkey(self, pubkey_bytes):
|
||||||
# Calls ed25519_decode_check_point() in secp256k1
|
# Calls ed25519_decode_check_point() in secp256k1
|
||||||
@@ -316,45 +396,58 @@ class XMRInterface(CoinInterface):
|
|||||||
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
|
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
|
||||||
return xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
return xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
||||||
|
|
||||||
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes:
|
def publishBLockTx(
|
||||||
|
self,
|
||||||
|
kbv: bytes,
|
||||||
|
Kbs: bytes,
|
||||||
|
output_amount: int,
|
||||||
|
feerate: int,
|
||||||
|
unlock_time: int = 0,
|
||||||
|
) -> bytes:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet('refresh')
|
|
||||||
|
|
||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
shared_addr = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
shared_addr = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
||||||
|
|
||||||
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time}
|
params = {
|
||||||
|
"destinations": [{"amount": output_amount, "address": shared_addr}],
|
||||||
|
"unlock_time": unlock_time,
|
||||||
|
}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params["priority"] = self._fee_priority
|
||||||
rv = self.rpc_wallet('transfer', params)
|
rv = self.rpc_wallet("transfer", params)
|
||||||
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
|
self._log.info(
|
||||||
tx_hash = bytes.fromhex(rv['tx_hash'])
|
"publishBLockTx %s to address_b58 %s",
|
||||||
|
self._log.id(rv["tx_hash"]),
|
||||||
|
self._log.addr(shared_addr),
|
||||||
|
)
|
||||||
|
tx_hash = bytes.fromhex(rv["tx_hash"])
|
||||||
|
|
||||||
return tx_hash
|
return tx_hash
|
||||||
|
|
||||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
def findTxB(
|
||||||
|
self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender
|
||||||
|
):
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
||||||
|
|
||||||
kbv_le = kbv[::-1]
|
kbv_le = kbv[::-1]
|
||||||
params = {
|
params = {
|
||||||
'restore_height': restore_height,
|
"restore_height": restore_height,
|
||||||
'filename': address_b58,
|
"filename": address_b58,
|
||||||
'address': address_b58,
|
"address": address_b58,
|
||||||
'viewkey': b2h(kbv_le),
|
"viewkey": b2h(kbv_le),
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.openWallet(address_b58)
|
self.openWallet(address_b58)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: F841
|
||||||
self.createWallet(params)
|
self.createWallet(params)
|
||||||
self.openWallet(address_b58)
|
self.openWallet(address_b58)
|
||||||
|
|
||||||
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
|
"""
|
||||||
|
|
||||||
'''
|
|
||||||
# Debug
|
# Debug
|
||||||
try:
|
try:
|
||||||
current_height = self.rpc_wallet('get_height')['height']
|
current_height = self.rpc_wallet('get_height')['height']
|
||||||
@@ -363,136 +456,219 @@ class XMRInterface(CoinInterface):
|
|||||||
self._log.info('rpc failed %s', str(e))
|
self._log.info('rpc failed %s', str(e))
|
||||||
current_height = None # If the transfer is available it will be deep enough
|
current_height = None # If the transfer is available it will be deep enough
|
||||||
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
||||||
'''
|
"""
|
||||||
params = {'transfer_type': 'available'}
|
params = {"transfer_type": "available"}
|
||||||
transfers = self.rpc_wallet('incoming_transfers', params)
|
transfers = self.rpc_wallet("incoming_transfers", params)
|
||||||
rv = None
|
rv = None
|
||||||
if 'transfers' in transfers:
|
if "transfers" in transfers:
|
||||||
for transfer in transfers['transfers']:
|
for transfer in transfers["transfers"]:
|
||||||
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
|
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
|
||||||
if not transfer['unlocked']:
|
if not transfer["unlocked"]:
|
||||||
full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']})
|
full_tx = self.rpc_wallet(
|
||||||
unlock_time = full_tx['transfer']['unlock_time']
|
"get_transfer_by_txid", {"txid": transfer["tx_hash"]}
|
||||||
|
)
|
||||||
|
unlock_time = full_tx["transfer"]["unlock_time"]
|
||||||
if unlock_time != 0:
|
if unlock_time != 0:
|
||||||
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
|
self._log.warning(
|
||||||
|
"Coin b lock txn is locked: {}, unlock_time {}".format(
|
||||||
|
transfer["tx_hash"], unlock_time
|
||||||
|
)
|
||||||
|
)
|
||||||
rv = -1
|
rv = -1
|
||||||
continue
|
continue
|
||||||
if transfer['amount'] == cb_swap_value:
|
if transfer["amount"] == cb_swap_value:
|
||||||
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']}
|
return {
|
||||||
|
"txid": transfer["tx_hash"],
|
||||||
|
"amount": transfer["amount"],
|
||||||
|
"height": (
|
||||||
|
0
|
||||||
|
if "block_height" not in transfer
|
||||||
|
else transfer["block_height"]
|
||||||
|
),
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash']))
|
self._log.warning(
|
||||||
|
"Incorrect amount detected for coin b lock txn: {}".format(
|
||||||
|
transfer["tx_hash"]
|
||||||
|
)
|
||||||
|
)
|
||||||
rv = -1
|
rv = -1
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def findTxnByHash(self, txid):
|
def findTxnByHash(self, txid):
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height']
|
current_height = self.rpc2("get_height", timeout=self._rpctimeout)[
|
||||||
self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid)
|
"height"
|
||||||
|
]
|
||||||
|
self._log.info(
|
||||||
|
f"findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.info('rpc failed %s', str(e))
|
self._log.info("rpc failed %s", str(e))
|
||||||
current_height = None # If the transfer is available it will be deep enough
|
current_height = (
|
||||||
|
None # If the transfer is available it will be deep enough
|
||||||
|
)
|
||||||
|
|
||||||
params = {'transfer_type': 'available'}
|
params = {"transfer_type": "available"}
|
||||||
rv = self.rpc_wallet('incoming_transfers', params)
|
rv = self.rpc_wallet("incoming_transfers", params)
|
||||||
if 'transfers' in rv:
|
if "transfers" in rv:
|
||||||
for transfer in rv['transfers']:
|
for transfer in rv["transfers"]:
|
||||||
if transfer['tx_hash'] == txid \
|
if transfer["tx_hash"] == txid and (
|
||||||
and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed):
|
current_height is None
|
||||||
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']}
|
or current_height - transfer["block_height"]
|
||||||
|
> self.blocks_confirmed
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"txid": transfer["tx_hash"],
|
||||||
|
"amount": transfer["amount"],
|
||||||
|
"height": transfer["block_height"],
|
||||||
|
}
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
|
def spendBLockTx(
|
||||||
'''
|
self,
|
||||||
|
chain_b_lock_txid: bytes,
|
||||||
|
address_to: str,
|
||||||
|
kbv: bytes,
|
||||||
|
kbs: bytes,
|
||||||
|
cb_swap_value: int,
|
||||||
|
b_fee_rate: int,
|
||||||
|
restore_height: int,
|
||||||
|
spend_actual_balance: bool = False,
|
||||||
|
lock_tx_vout=None,
|
||||||
|
) -> bytes:
|
||||||
|
"""
|
||||||
Notes:
|
Notes:
|
||||||
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
|
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
|
||||||
'''
|
"""
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
Kbs = self.getPubkey(kbs)
|
Kbs = self.getPubkey(kbs)
|
||||||
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
||||||
|
|
||||||
wallet_filename = address_b58 + '_spend'
|
wallet_filename = address_b58 + "_spend"
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'filename': wallet_filename,
|
"filename": wallet_filename,
|
||||||
'address': address_b58,
|
"address": address_b58,
|
||||||
'viewkey': b2h(kbv[::-1]),
|
"viewkey": b2h(kbv[::-1]),
|
||||||
'spendkey': b2h(kbs[::-1]),
|
"spendkey": b2h(kbs[::-1]),
|
||||||
'restore_height': restore_height,
|
"restore_height": restore_height,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.openWallet(wallet_filename)
|
self.openWallet(wallet_filename)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: F841
|
||||||
self.createWallet(params)
|
self.createWallet(params)
|
||||||
self.openWallet(wallet_filename)
|
self.openWallet(wallet_filename)
|
||||||
|
|
||||||
self.rpc_wallet('refresh')
|
rv = self.rpc_wallet("get_balance")
|
||||||
rv = self.rpc_wallet('get_balance')
|
if rv["balance"] < cb_swap_value:
|
||||||
if rv['balance'] < cb_swap_value:
|
self._log.warning("Balance is too low, checking for existing spend.")
|
||||||
self._log.warning('Balance is too low, checking for existing spend.')
|
txns = self.rpc_wallet("get_transfers", {"out": True})
|
||||||
txns = self.rpc_wallet('get_transfers', {'out': True})
|
if "out" in txns:
|
||||||
if 'out' in txns:
|
txns = txns["out"]
|
||||||
txns = txns['out']
|
|
||||||
if len(txns) > 0:
|
if len(txns) > 0:
|
||||||
txid = txns[0]['txid']
|
txid = txns[0]["txid"]
|
||||||
self._log.warning(f'spendBLockTx detected spending tx: {txid}.')
|
self._log.warning(f"spendBLockTx detected spending tx: {txid}.")
|
||||||
if txns[0]['address'] == address_b58:
|
|
||||||
|
# Should check for address_to, but only the from address is found in the output
|
||||||
|
if txns[0]["address"] == address_b58:
|
||||||
return bytes.fromhex(txid)
|
return bytes.fromhex(txid)
|
||||||
|
|
||||||
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
|
self._log.error(
|
||||||
|
"wallet {} balance {}, expected {}".format(
|
||||||
|
wallet_filename, rv["balance"], cb_swap_value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if not spend_actual_balance:
|
if not spend_actual_balance:
|
||||||
raise TemporaryError('Invalid balance')
|
raise TemporaryError("Invalid balance")
|
||||||
|
|
||||||
if spend_actual_balance and rv['balance'] != cb_swap_value:
|
if spend_actual_balance and rv["balance"] != cb_swap_value:
|
||||||
self._log.warning('Spending actual balance {}, not swap value {}.'.format(rv['balance'], cb_swap_value))
|
self._log.warning(
|
||||||
cb_swap_value = rv['balance']
|
"Spending actual balance {}, not swap value {}.".format(
|
||||||
if rv['unlocked_balance'] < cb_swap_value:
|
rv["balance"], cb_swap_value
|
||||||
self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock']))
|
)
|
||||||
raise TemporaryError('Invalid unlocked_balance')
|
)
|
||||||
|
cb_swap_value = rv["balance"]
|
||||||
|
if rv["unlocked_balance"] < cb_swap_value:
|
||||||
|
self._log.error(
|
||||||
|
"wallet {} balance {}, expected {}, blocks_to_unlock {}".format(
|
||||||
|
wallet_filename,
|
||||||
|
rv["unlocked_balance"],
|
||||||
|
cb_swap_value,
|
||||||
|
rv["blocks_to_unlock"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
raise TemporaryError("Invalid unlocked_balance")
|
||||||
|
|
||||||
params = {'address': address_to}
|
params = {"address": address_to}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params["priority"] = self._fee_priority
|
||||||
|
|
||||||
rv = self.rpc_wallet('sweep_all', params)
|
rv = self.rpc_wallet("sweep_all", params)
|
||||||
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])
|
||||||
|
|
||||||
def withdrawCoin(self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False) -> str:
|
def withdrawCoin(
|
||||||
|
self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False
|
||||||
|
) -> str:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet('refresh')
|
|
||||||
|
|
||||||
if sweepall:
|
if sweepall:
|
||||||
balance = self.rpc_wallet('get_balance')
|
balance = self.rpc_wallet("get_balance")
|
||||||
if balance['balance'] != balance['unlocked_balance']:
|
if balance["balance"] != balance["unlocked_balance"]:
|
||||||
raise ValueError('Balance must be fully confirmed to use sweep all.')
|
raise ValueError(
|
||||||
self._log.info('XMR {} sweep_all.'.format('estimate fee' if estimate_fee else 'withdraw'))
|
"Balance must be fully confirmed to use sweep all."
|
||||||
self._log.debug('XMR balance: {}'.format(balance['balance']))
|
)
|
||||||
params = {'address': addr_to, 'do_not_relay': estimate_fee}
|
self._log.info(
|
||||||
|
"{} {} sweep_all.".format(
|
||||||
|
self.ticker_str(),
|
||||||
|
"estimate fee" if estimate_fee else "withdraw",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._log.debug(
|
||||||
|
"{} balance: {}".format(self.ticker_str(), balance["balance"])
|
||||||
|
)
|
||||||
|
params = {
|
||||||
|
"address": addr_to,
|
||||||
|
"do_not_relay": estimate_fee,
|
||||||
|
"subaddr_indices_all": True,
|
||||||
|
}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params["priority"] = self._fee_priority
|
||||||
rv = self.rpc_wallet('sweep_all', params)
|
rv = self.rpc_wallet("sweep_all", params)
|
||||||
if estimate_fee:
|
if estimate_fee:
|
||||||
return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])}
|
return {
|
||||||
return rv['tx_hash_list'][0]
|
"num_txns": len(rv["fee_list"]),
|
||||||
|
"sum_amount": sum(rv["amount_list"]),
|
||||||
|
"sum_fee": sum(rv["fee_list"]),
|
||||||
|
"sum_weight": sum(rv["weight_list"]),
|
||||||
|
}
|
||||||
|
return rv["tx_hash_list"][0]
|
||||||
|
|
||||||
value_sats: int = self.make_int(value)
|
value_sats: int = self.make_int(value)
|
||||||
params = {'destinations': [{'amount': value_sats, 'address': addr_to}], 'do_not_relay': estimate_fee}
|
params = {
|
||||||
|
"destinations": [{"amount": value_sats, "address": addr_to}],
|
||||||
|
"do_not_relay": estimate_fee,
|
||||||
|
}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params["priority"] = self._fee_priority
|
||||||
rv = self.rpc_wallet('transfer', params)
|
rv = self.rpc_wallet("transfer", params)
|
||||||
if estimate_fee:
|
if estimate_fee:
|
||||||
return {'num_txns': 1, 'sum_amount': rv['amount'], 'sum_fee': rv['fee'], 'sum_weight': rv['weight']}
|
return {
|
||||||
return rv['tx_hash']
|
"num_txns": 1,
|
||||||
|
"sum_amount": rv["amount"],
|
||||||
|
"sum_fee": rv["fee"],
|
||||||
|
"sum_weight": rv["weight"],
|
||||||
|
}
|
||||||
|
return rv["tx_hash"]
|
||||||
|
|
||||||
def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str:
|
def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str:
|
||||||
return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True)
|
return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True)
|
||||||
@@ -502,7 +678,7 @@ class XMRInterface(CoinInterface):
|
|||||||
try:
|
try:
|
||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix)
|
||||||
wallet_file = address_b58 + '_spend'
|
wallet_file = address_b58 + "_spend"
|
||||||
try:
|
try:
|
||||||
self.openWallet(wallet_file)
|
self.openWallet(wallet_file)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -510,54 +686,59 @@ class XMRInterface(CoinInterface):
|
|||||||
try:
|
try:
|
||||||
self.openWallet(wallet_file)
|
self.openWallet(wallet_file)
|
||||||
except Exception:
|
except Exception:
|
||||||
self._log.info(f'showLockTransfers trying to create wallet for address {address_b58}.')
|
self._log.info(
|
||||||
|
f"showLockTransfers trying to create wallet for address {address_b58}."
|
||||||
|
)
|
||||||
kbv_le = kbv[::-1]
|
kbv_le = kbv[::-1]
|
||||||
params = {
|
params = {
|
||||||
'restore_height': restore_height,
|
"restore_height": restore_height,
|
||||||
'filename': address_b58,
|
"filename": address_b58,
|
||||||
'address': address_b58,
|
"address": address_b58,
|
||||||
'viewkey': b2h(kbv_le),
|
"viewkey": b2h(kbv_le),
|
||||||
}
|
}
|
||||||
self.createWallet(params)
|
self.createWallet(params)
|
||||||
self.openWallet(address_b58)
|
self.openWallet(address_b58)
|
||||||
|
|
||||||
self.rpc_wallet('refresh')
|
rv = self.rpc_wallet(
|
||||||
|
"get_transfers",
|
||||||
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
|
{"in": True, "out": True, "pending": True, "failed": True},
|
||||||
rv['filename'] = wallet_file
|
)
|
||||||
|
rv["filename"] = wallet_file
|
||||||
return rv
|
return rv
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {'error': str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
def getSpendableBalance(self) -> int:
|
def getSpendableBalance(self) -> int:
|
||||||
with self._mx_wallet:
|
with self._mx_wallet:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
|
|
||||||
self.rpc_wallet('refresh')
|
balance_info = self.rpc_wallet("get_balance")
|
||||||
balance_info = self.rpc_wallet('get_balance')
|
return balance_info["unlocked_balance"]
|
||||||
return balance_info['unlocked_balance']
|
|
||||||
|
|
||||||
def changeWalletPassword(self, old_password, new_password):
|
def changeWalletPassword(self, old_password, new_password):
|
||||||
self._log.info('changeWalletPassword - {}'.format(self.ticker()))
|
self._log.info("changeWalletPassword - {}".format(self.ticker()))
|
||||||
orig_password = self._wallet_password
|
orig_password = self._wallet_password
|
||||||
if old_password != '':
|
if old_password != "":
|
||||||
self._wallet_password = old_password
|
self._wallet_password = old_password
|
||||||
try:
|
try:
|
||||||
self.openWallet(self._wallet_filename)
|
self.openWallet(self._wallet_filename)
|
||||||
self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
|
self.rpc_wallet(
|
||||||
|
"change_wallet_password",
|
||||||
|
{"old_password": old_password, "new_password": new_password},
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._wallet_password = orig_password
|
self._wallet_password = orig_password
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def unlockWallet(self, password: str) -> None:
|
def unlockWallet(self, password: str) -> None:
|
||||||
self._log.info('unlockWallet - {}'.format(self.ticker()))
|
self._log.info("unlockWallet - {}".format(self.ticker()))
|
||||||
self._wallet_password = password
|
self._wallet_password = password
|
||||||
|
|
||||||
if not self._have_checked_seed:
|
if not self._have_checked_seed:
|
||||||
self._sc.checkWalletSeed(self.coin_type())
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
|
||||||
def lockWallet(self) -> None:
|
def lockWallet(self) -> None:
|
||||||
self._log.info('lockWallet - {}'.format(self.ticker()))
|
self._log.info("lockWallet - {}".format(self.ticker()))
|
||||||
self._wallet_password = None
|
self._wallet_password = None
|
||||||
|
|
||||||
def isAddressMine(self, address):
|
def isAddressMine(self, address):
|
||||||
@@ -566,7 +747,14 @@ class XMRInterface(CoinInterface):
|
|||||||
|
|
||||||
def ensureFunds(self, amount: int) -> None:
|
def ensureFunds(self, amount: int) -> None:
|
||||||
if self.getSpendableBalance() < amount:
|
if self.getSpendableBalance() < amount:
|
||||||
raise ValueError('Balance too low')
|
raise ValueError("Balance too low")
|
||||||
|
|
||||||
def getTransaction(self, txid: bytes):
|
def getTransaction(self, txid: bytes):
|
||||||
return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]})
|
return self.rpc2(
|
||||||
|
"get_transactions",
|
||||||
|
{
|
||||||
|
"txs_hashes": [
|
||||||
|
txid.hex(),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package basicswap;
|
|
||||||
|
|
||||||
/* Step 1, seller -> network */
|
|
||||||
message OfferMessage {
|
|
||||||
uint32 protocol_version = 1;
|
|
||||||
uint32 coin_from = 2;
|
|
||||||
uint32 coin_to = 3;
|
|
||||||
uint64 amount_from = 4;
|
|
||||||
uint64 amount_to = 5;
|
|
||||||
uint64 min_bid_amount = 6;
|
|
||||||
uint64 time_valid = 7;
|
|
||||||
enum LockType {
|
|
||||||
NOT_SET = 0;
|
|
||||||
SEQUENCE_LOCK_BLOCKS = 1;
|
|
||||||
SEQUENCE_LOCK_TIME = 2;
|
|
||||||
ABS_LOCK_BLOCKS = 3;
|
|
||||||
ABS_LOCK_TIME = 4;
|
|
||||||
}
|
|
||||||
LockType lock_type = 8;
|
|
||||||
uint32 lock_value = 9;
|
|
||||||
uint32 swap_type = 10;
|
|
||||||
|
|
||||||
/* optional */
|
|
||||||
string proof_address = 11;
|
|
||||||
string proof_signature = 12;
|
|
||||||
bytes pkhash_seller = 13;
|
|
||||||
bytes secret_hash = 14;
|
|
||||||
|
|
||||||
uint64 fee_rate_from = 15;
|
|
||||||
uint64 fee_rate_to = 16;
|
|
||||||
|
|
||||||
bool amount_negotiable = 17;
|
|
||||||
bool rate_negotiable = 18;
|
|
||||||
|
|
||||||
bytes proof_utxos = 19; /* 32 byte txid 2 byte vout, repeated */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 2, buyer -> seller */
|
|
||||||
message BidMessage {
|
|
||||||
uint32 protocol_version = 1;
|
|
||||||
bytes offer_msg_id = 2;
|
|
||||||
uint64 time_valid = 3; /* seconds bid is valid for */
|
|
||||||
uint64 amount = 4; /* amount of amount_from bid is for */
|
|
||||||
uint64 amount_to = 5;
|
|
||||||
bytes pkhash_buyer = 6; /* buyer's address to receive amount_from */
|
|
||||||
string proof_address = 7;
|
|
||||||
string proof_signature = 8;
|
|
||||||
|
|
||||||
bytes proof_utxos = 9; /* 32 byte txid 2 byte vout, repeated */
|
|
||||||
|
|
||||||
/* optional */
|
|
||||||
bytes pkhash_buyer_to = 13; /* When pubkey hash is different on the to-chain */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For tests */
|
|
||||||
message BidMessage_test {
|
|
||||||
uint32 protocol_version = 1;
|
|
||||||
bytes offer_msg_id = 2;
|
|
||||||
uint64 time_valid = 3;
|
|
||||||
uint64 amount = 4;
|
|
||||||
uint64 rate = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 3, seller -> buyer */
|
|
||||||
message BidAcceptMessage {
|
|
||||||
bytes bid_msg_id = 1;
|
|
||||||
bytes initiate_txid = 2;
|
|
||||||
bytes contract_script = 3;
|
|
||||||
bytes pkhash_seller = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OfferRevokeMessage {
|
|
||||||
bytes offer_msg_id = 1;
|
|
||||||
bytes signature = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BidRejectMessage {
|
|
||||||
bytes bid_msg_id = 1;
|
|
||||||
uint32 reject_code = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message XmrBidMessage {
|
|
||||||
/* MSG1L, F -> L */
|
|
||||||
uint32 protocol_version = 1;
|
|
||||||
bytes offer_msg_id = 2;
|
|
||||||
uint64 time_valid = 3; /* seconds bid is valid for */
|
|
||||||
uint64 amount = 4; /* amount of amount_from bid is for */
|
|
||||||
uint64 amount_to = 5;
|
|
||||||
|
|
||||||
bytes pkaf = 6;
|
|
||||||
|
|
||||||
bytes kbvf = 7;
|
|
||||||
bytes kbsf_dleag = 8;
|
|
||||||
|
|
||||||
bytes dest_af = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
message XmrSplitMessage {
|
|
||||||
bytes msg_id = 1;
|
|
||||||
uint32 msg_type = 2; /* 1 XmrBid, 2 XmrBidAccept */
|
|
||||||
uint32 sequence = 3;
|
|
||||||
bytes dleag = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message XmrBidAcceptMessage {
|
|
||||||
bytes bid_msg_id = 1;
|
|
||||||
|
|
||||||
bytes pkal = 2;
|
|
||||||
bytes kbvl = 3;
|
|
||||||
bytes kbsl_dleag = 4;
|
|
||||||
|
|
||||||
/* MSG2F */
|
|
||||||
bytes a_lock_tx = 5;
|
|
||||||
bytes a_lock_tx_script = 6;
|
|
||||||
bytes a_lock_refund_tx = 7;
|
|
||||||
bytes a_lock_refund_tx_script = 8;
|
|
||||||
bytes a_lock_refund_spend_tx = 9;
|
|
||||||
bytes al_lock_refund_tx_sig = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
message XmrBidLockTxSigsMessage {
|
|
||||||
/* MSG3L */
|
|
||||||
bytes bid_msg_id = 1;
|
|
||||||
bytes af_lock_refund_spend_tx_esig = 2;
|
|
||||||
bytes af_lock_refund_tx_sig = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message XmrBidLockSpendTxMessage {
|
|
||||||
/* MSG4F */
|
|
||||||
bytes bid_msg_id = 1;
|
|
||||||
bytes a_lock_spend_tx = 2;
|
|
||||||
bytes kal_sig = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message XmrBidLockReleaseMessage {
|
|
||||||
/* MSG5F */
|
|
||||||
bytes bid_msg_id = 1;
|
|
||||||
bytes al_lock_spend_tx_esig = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ADSBidIntentMessage {
|
|
||||||
/* L -> F Sent from bidder, construct a reverse bid */
|
|
||||||
uint32 protocol_version = 1;
|
|
||||||
bytes offer_msg_id = 2;
|
|
||||||
uint64 time_valid = 3; /* seconds bid is valid for */
|
|
||||||
uint64 amount_from = 4; /* amount of offer.coin_from bid is for */
|
|
||||||
uint64 amount_to = 5; /* amount of offer.coin_to bid is for, equivalent to bid.amount */
|
|
||||||
}
|
|
||||||
|
|
||||||
message ADSBidIntentAcceptMessage {
|
|
||||||
/* F -> L Sent from offerer, construct a reverse bid */
|
|
||||||
bytes bid_msg_id = 1;
|
|
||||||
|
|
||||||
bytes pkaf = 2;
|
|
||||||
bytes kbvf = 3;
|
|
||||||
bytes kbsf_dleag = 4;
|
|
||||||
bytes dest_af = 5;
|
|
||||||
}
|
|
||||||
266
basicswap/messages_npb.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 tecnovert
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
|
||||||
|
1 I64 fixed64, sfixed64, double
|
||||||
|
2 LEN string, bytes, embedded messages, packed repeated fields
|
||||||
|
5 I32 fixed32, sfixed32, float
|
||||||
|
|
||||||
|
Don't encode fields of default values.
|
||||||
|
When decoding initialise all fields not set from data.
|
||||||
|
|
||||||
|
protobuf ParseFromString would reset the whole object, from_bytes won't.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from basicswap.util.integer import encode_varint, decode_varint
|
||||||
|
|
||||||
|
|
||||||
|
class NonProtobufClass:
|
||||||
|
def __init__(self, init_all: bool = True, **kwargs):
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
found_field: bool = False
|
||||||
|
for field_num, v in self._map.items():
|
||||||
|
field_name, wire_type, field_type = v
|
||||||
|
if field_name == key:
|
||||||
|
setattr(self, field_name, value)
|
||||||
|
found_field = True
|
||||||
|
break
|
||||||
|
if found_field is False:
|
||||||
|
raise ValueError(f"got an unexpected keyword argument '{key}'")
|
||||||
|
|
||||||
|
if init_all:
|
||||||
|
self.init_fields()
|
||||||
|
|
||||||
|
def init_fields(self) -> None:
|
||||||
|
# Set default values for missing fields
|
||||||
|
for field_num, v in self._map.items():
|
||||||
|
field_name, wire_type, field_type = v
|
||||||
|
if hasattr(self, field_name):
|
||||||
|
continue
|
||||||
|
if wire_type == 0:
|
||||||
|
setattr(self, field_name, 0)
|
||||||
|
elif wire_type == 2:
|
||||||
|
if field_type == 1:
|
||||||
|
setattr(self, field_name, str())
|
||||||
|
else:
|
||||||
|
setattr(self, field_name, bytes())
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown wire_type {wire_type}")
|
||||||
|
|
||||||
|
def to_bytes(self) -> bytes:
|
||||||
|
rv = bytes()
|
||||||
|
|
||||||
|
for field_num, v in self._map.items():
|
||||||
|
field_name, wire_type, field_type = v
|
||||||
|
if not hasattr(self, field_name):
|
||||||
|
continue
|
||||||
|
field_value = getattr(self, field_name)
|
||||||
|
tag = (field_num << 3) | wire_type
|
||||||
|
if wire_type == 0:
|
||||||
|
if field_value == 0:
|
||||||
|
continue
|
||||||
|
rv += encode_varint(tag)
|
||||||
|
rv += encode_varint(field_value)
|
||||||
|
elif wire_type == 2:
|
||||||
|
if len(field_value) == 0:
|
||||||
|
continue
|
||||||
|
rv += encode_varint(tag)
|
||||||
|
if isinstance(field_value, str):
|
||||||
|
field_value = field_value.encode("utf-8")
|
||||||
|
rv += encode_varint(len(field_value))
|
||||||
|
rv += field_value
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown wire_type {wire_type}")
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def from_bytes(self, b: bytes, init_all: bool = True) -> None:
|
||||||
|
max_len: int = len(b)
|
||||||
|
o: int = 0
|
||||||
|
while o < max_len:
|
||||||
|
tag, lv = decode_varint(b, o)
|
||||||
|
o += lv
|
||||||
|
wire_type = tag & 7
|
||||||
|
field_num = tag >> 3
|
||||||
|
|
||||||
|
field_name, wire_type_expect, field_type = self._map[field_num]
|
||||||
|
if wire_type != wire_type_expect:
|
||||||
|
raise ValueError(
|
||||||
|
f"Unexpected wire_type {wire_type} for field {field_num}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if wire_type == 0:
|
||||||
|
field_value, lv = decode_varint(b, o)
|
||||||
|
o += lv
|
||||||
|
elif wire_type == 2:
|
||||||
|
field_len, lv = decode_varint(b, o)
|
||||||
|
o += lv
|
||||||
|
field_value = b[o : o + field_len]
|
||||||
|
o += field_len
|
||||||
|
if field_type == 1:
|
||||||
|
field_value = field_value.decode("utf-8")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown wire_type {wire_type}")
|
||||||
|
|
||||||
|
setattr(self, field_name, field_value)
|
||||||
|
|
||||||
|
if init_all:
|
||||||
|
self.init_fields()
|
||||||
|
|
||||||
|
|
||||||
|
class OfferMessage(NonProtobufClass):
|
||||||
|
_map = {
|
||||||
|
1: ("protocol_version", 0, 0),
|
||||||
|
2: ("coin_from", 0, 0),
|
||||||
|
3: ("coin_to", 0, 0),
|
||||||
|
4: ("amount_from", 0, 0),
|
||||||
|
5: ("amount_to", 0, 0),
|
||||||
|
6: ("min_bid_amount", 0, 0),
|
||||||
|
7: ("time_valid", 0, 0),
|
||||||
|
8: ("lock_type", 0, 0),
|
||||||
|
9: ("lock_value", 0, 0),
|
||||||
|
10: ("swap_type", 0, 0),
|
||||||
|
11: ("proof_address", 2, 1),
|
||||||
|
12: ("proof_signature", 2, 1),
|
||||||
|
13: ("pkhash_seller", 2, 0),
|
||||||
|
14: ("secret_hash", 2, 0),
|
||||||
|
15: ("fee_rate_from", 0, 0),
|
||||||
|
16: ("fee_rate_to", 0, 0),
|
||||||
|
17: ("amount_negotiable", 0, 2),
|
||||||
|
18: ("rate_negotiable", 0, 2),
|
||||||
|
19: ("proof_utxos", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BidMessage(NonProtobufClass):
|
||||||
|
_map = {
|
||||||
|
1: ("protocol_version", 0, 0),
|
||||||
|
2: ("offer_msg_id", 2, 0),
|
||||||
|
3: ("time_valid", 0, 0),
|
||||||
|
4: ("amount", 0, 0),
|
||||||
|
5: ("amount_to", 0, 0),
|
||||||
|
6: ("pkhash_buyer", 2, 0),
|
||||||
|
7: ("proof_address", 2, 1),
|
||||||
|
8: ("proof_signature", 2, 1),
|
||||||
|
9: ("proof_utxos", 2, 0),
|
||||||
|
10: ("pkhash_buyer_to", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BidAcceptMessage(NonProtobufClass):
|
||||||
|
# Step 3, seller -> buyer
|
||||||
|
_map = {
|
||||||
|
1: ("bid_msg_id", 2, 0),
|
||||||
|
2: ("initiate_txid", 2, 0),
|
||||||
|
3: ("contract_script", 2, 0),
|
||||||
|
4: ("pkhash_seller", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class OfferRevokeMessage(NonProtobufClass):
|
||||||
|
_map = {
|
||||||
|
1: ("offer_msg_id", 2, 0),
|
||||||
|
2: ("signature", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BidRejectMessage(NonProtobufClass):
|
||||||
|
_map = {
|
||||||
|
1: ("bid_msg_id", 2, 0),
|
||||||
|
2: ("reject_code", 0, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class XmrBidMessage(NonProtobufClass):
|
||||||
|
# MSG1L, F -> L
|
||||||
|
_map = {
|
||||||
|
1: ("protocol_version", 0, 0),
|
||||||
|
2: ("offer_msg_id", 2, 0),
|
||||||
|
3: ("time_valid", 0, 0),
|
||||||
|
4: ("amount", 0, 0),
|
||||||
|
5: ("amount_to", 0, 0),
|
||||||
|
6: ("pkaf", 2, 0),
|
||||||
|
7: ("kbvf", 2, 0),
|
||||||
|
8: ("kbsf_dleag", 2, 0),
|
||||||
|
9: ("dest_af", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class XmrSplitMessage(NonProtobufClass):
|
||||||
|
_map = {
|
||||||
|
1: ("msg_id", 2, 0),
|
||||||
|
2: ("msg_type", 0, 0),
|
||||||
|
3: ("sequence", 0, 0),
|
||||||
|
4: ("dleag", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class XmrBidAcceptMessage(NonProtobufClass):
|
||||||
|
_map = {
|
||||||
|
1: ("bid_msg_id", 2, 0),
|
||||||
|
2: ("pkal", 2, 0),
|
||||||
|
3: ("kbvl", 2, 0),
|
||||||
|
4: ("kbsl_dleag", 2, 0),
|
||||||
|
# MSG2F
|
||||||
|
5: ("a_lock_tx", 2, 0),
|
||||||
|
6: ("a_lock_tx_script", 2, 0),
|
||||||
|
7: ("a_lock_refund_tx", 2, 0),
|
||||||
|
8: ("a_lock_refund_tx_script", 2, 0),
|
||||||
|
9: ("a_lock_refund_spend_tx", 2, 0),
|
||||||
|
10: ("al_lock_refund_tx_sig", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class XmrBidLockTxSigsMessage(NonProtobufClass):
|
||||||
|
# MSG3L
|
||||||
|
_map = {
|
||||||
|
1: ("bid_msg_id", 2, 0),
|
||||||
|
2: ("af_lock_refund_spend_tx_esig", 2, 0),
|
||||||
|
3: ("af_lock_refund_tx_sig", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class XmrBidLockSpendTxMessage(NonProtobufClass):
|
||||||
|
# MSG4F
|
||||||
|
_map = {
|
||||||
|
1: ("bid_msg_id", 2, 0),
|
||||||
|
2: ("a_lock_spend_tx", 2, 0),
|
||||||
|
3: ("kal_sig", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class XmrBidLockReleaseMessage(NonProtobufClass):
|
||||||
|
# MSG5F
|
||||||
|
_map = {
|
||||||
|
1: ("bid_msg_id", 2, 0),
|
||||||
|
2: ("al_lock_spend_tx_esig", 2, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ADSBidIntentMessage(NonProtobufClass):
|
||||||
|
# L -> F Sent from bidder, construct a reverse bid
|
||||||
|
_map = {
|
||||||
|
1: ("protocol_version", 0, 0),
|
||||||
|
2: ("offer_msg_id", 2, 0),
|
||||||
|
3: ("time_valid", 0, 0),
|
||||||
|
4: ("amount_from", 0, 0),
|
||||||
|
5: ("amount_to", 0, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ADSBidIntentAcceptMessage(NonProtobufClass):
|
||||||
|
# F -> L Sent from offerer, construct a reverse bid
|
||||||
|
_map = {
|
||||||
|
1: ("bid_msg_id", 2, 0),
|
||||||
|
2: ("pkaf", 2, 0),
|
||||||
|
3: ("kbvf", 2, 0),
|
||||||
|
4: ("kbsf_dleag", 2, 0),
|
||||||
|
5: ("dest_af", 2, 0),
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
||||||
# source: messages.proto
|
|
||||||
# Protobuf Python Version: 4.25.3
|
|
||||||
"""Generated protocol buffer code."""
|
|
||||||
from google.protobuf import descriptor as _descriptor
|
|
||||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
||||||
from google.protobuf import symbol_database as _symbol_database
|
|
||||||
from google.protobuf.internal import builder as _builder
|
|
||||||
# @@protoc_insertion_point(imports)
|
|
||||||
|
|
||||||
_sym_db = _symbol_database.Default()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xc0\x04\n\x0cOfferMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x11\n\tcoin_from\x18\x02 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x03 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x06 \x01(\x04\x12\x12\n\ntime_valid\x18\x07 \x01(\x04\x12\x33\n\tlock_type\x18\x08 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\t \x01(\r\x12\x11\n\tswap_type\x18\n \x01(\r\x12\x15\n\rproof_address\x18\x0b \x01(\t\x12\x17\n\x0fproof_signature\x18\x0c \x01(\t\x12\x15\n\rpkhash_seller\x18\r \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\x0e \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0f \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x10 \x01(\x04\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\x12\x13\n\x0bproof_utxos\x18\x13 \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xe7\x01\n\nBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x06 \x01(\x0c\x12\x15\n\rproof_address\x18\x07 \x01(\t\x12\x17\n\x0fproof_signature\x18\x08 \x01(\t\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\x12\x17\n\x0fpkhash_buyer_to\x18\r \x01(\x0c\"s\n\x0f\x42idMessage_test\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\"m\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\x12\x15\n\rpkhash_seller\x18\x04 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb7\x01\n\rXmrBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x0c\n\x04pkaf\x18\x06 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x07 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x08 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\t \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x03 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x04 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x05 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x07 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\x08 \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\t \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\n \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x81\x01\n\x13\x41\x44SBidIntentMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
|
|
||||||
|
|
||||||
_globals = globals()
|
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', _globals)
|
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
|
||||||
DESCRIPTOR._options = None
|
|
||||||
_globals['_OFFERMESSAGE']._serialized_start=30
|
|
||||||
_globals['_OFFERMESSAGE']._serialized_end=606
|
|
||||||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_start=493
|
|
||||||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_end=606
|
|
||||||
_globals['_BIDMESSAGE']._serialized_start=609
|
|
||||||
_globals['_BIDMESSAGE']._serialized_end=840
|
|
||||||
_globals['_BIDMESSAGE_TEST']._serialized_start=842
|
|
||||||
_globals['_BIDMESSAGE_TEST']._serialized_end=957
|
|
||||||
_globals['_BIDACCEPTMESSAGE']._serialized_start=959
|
|
||||||
_globals['_BIDACCEPTMESSAGE']._serialized_end=1068
|
|
||||||
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1070
|
|
||||||
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1131
|
|
||||||
_globals['_BIDREJECTMESSAGE']._serialized_start=1133
|
|
||||||
_globals['_BIDREJECTMESSAGE']._serialized_end=1192
|
|
||||||
_globals['_XMRBIDMESSAGE']._serialized_start=1195
|
|
||||||
_globals['_XMRBIDMESSAGE']._serialized_end=1378
|
|
||||||
_globals['_XMRSPLITMESSAGE']._serialized_start=1380
|
|
||||||
_globals['_XMRSPLITMESSAGE']._serialized_end=1464
|
|
||||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1467
|
|
||||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1723
|
|
||||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1725
|
|
||||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1839
|
|
||||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1841
|
|
||||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1929
|
|
||||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1931
|
|
||||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=2008
|
|
||||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=2011
|
|
||||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2140
|
|
||||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2142
|
|
||||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2254
|
|
||||||
# @@protoc_insertion_point(module_scope)
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Message 2 bytes msg_class, 4 bytes length, [ 2 bytes msg_type, payload ]
|
Message 2 bytes msg_class, 4 bytes length, [ 2 bytes msg_type, payload ]
|
||||||
|
|
||||||
Handshake procedure:
|
Handshake procedure:
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
Both nodes are initialised
|
Both nodes are initialised
|
||||||
|
|
||||||
XChaCha20_Poly1305 mac is 16bytes
|
XChaCha20_Poly1305 mac is 16bytes
|
||||||
'''
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import queue
|
import queue
|
||||||
@@ -36,11 +36,12 @@ from Crypto.Cipher import ChaCha20_Poly1305 # TODO: Add to libsecp256k1/coincur
|
|||||||
from coincurve.keys import PrivateKey, PublicKey
|
from coincurve.keys import PrivateKey, PublicKey
|
||||||
from basicswap.contrib.rfc6979 import (
|
from basicswap.contrib.rfc6979 import (
|
||||||
rfc6979_hmac_sha256_initialize,
|
rfc6979_hmac_sha256_initialize,
|
||||||
rfc6979_hmac_sha256_generate)
|
rfc6979_hmac_sha256_generate,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
START_TOKEN = 0xabcd
|
START_TOKEN = 0xABCD
|
||||||
MSG_START_TOKEN = START_TOKEN.to_bytes(2, 'big')
|
MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
|
||||||
|
|
||||||
MSG_MAX_SIZE = 0x200000 # 2MB
|
MSG_MAX_SIZE = 0x200000 # 2MB
|
||||||
|
|
||||||
@@ -63,35 +64,37 @@ class NetMessageTypes(IntEnum):
|
|||||||
return value in cls._value2member_map_
|
return value in cls._value2member_map_
|
||||||
|
|
||||||
|
|
||||||
'''
|
"""
|
||||||
class NetMessage:
|
class NetMessage:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._msg_class = None # 2 bytes
|
self._msg_class = None # 2 bytes
|
||||||
self._len = None # 4 bytes
|
self._len = None # 4 bytes
|
||||||
self._msg_type = None # 2 bytes
|
self._msg_type = None # 2 bytes
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
# Ensure handshake keys are not reused by including the time in the msg, mac and key hash
|
# Ensure handshake keys are not reused by including the time in the msg, mac and key hash
|
||||||
# Verify timestamp is not too old
|
# Verify timestamp is not too old
|
||||||
# Add keys to db to catch concurrent attempts, records can be cleared periodically, the timestamp should catch older replay attempts
|
# Add keys to db to catch concurrent attempts, records can be cleared periodically, the timestamp should catch older replay attempts
|
||||||
class MsgHandshake:
|
class MsgHandshake:
|
||||||
__slots__ = ('_timestamp', '_ephem_pk', '_ct', '_mac')
|
__slots__ = ("_timestamp", "_ephem_pk", "_ct", "_mac")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def encode_aad(self): # Additional Authenticated Data
|
def encode_aad(self): # Additional Authenticated Data
|
||||||
return int(NetMessageTypes.HANDSHAKE).to_bytes(2, 'big') + \
|
return (
|
||||||
self._timestamp.to_bytes(8, 'big') + \
|
int(NetMessageTypes.HANDSHAKE).to_bytes(2, "big")
|
||||||
self._ephem_pk
|
+ self._timestamp.to_bytes(8, "big")
|
||||||
|
+ self._ephem_pk
|
||||||
|
)
|
||||||
|
|
||||||
def encode(self):
|
def encode(self):
|
||||||
return self.encode_aad() + self._ct + self._mac
|
return self.encode_aad() + self._ct + self._mac
|
||||||
|
|
||||||
def decode(self, msg_mv):
|
def decode(self, msg_mv):
|
||||||
o = 2
|
o = 2
|
||||||
self._timestamp = int.from_bytes(msg_mv[o: o + 8], 'big')
|
self._timestamp = int.from_bytes(msg_mv[o : o + 8], "big")
|
||||||
o += 8
|
o += 8
|
||||||
self._ephem_pk = bytes(msg_mv[o : o + 33])
|
self._ephem_pk = bytes(msg_mv[o : o + 33])
|
||||||
o += 33
|
o += 33
|
||||||
@@ -101,11 +104,31 @@ class MsgHandshake:
|
|||||||
|
|
||||||
class Peer:
|
class Peer:
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'_mx', '_pubkey', '_address', '_socket', '_version', '_ready', '_incoming',
|
"_mx",
|
||||||
'_connected_at', '_last_received_at', '_bytes_sent', '_bytes_received',
|
"_pubkey",
|
||||||
'_receiving_length', '_receiving_buffer', '_recv_messages', '_misbehaving_score',
|
"_address",
|
||||||
'_ke', '_km', '_dir', '_sent_nonce', '_recv_nonce', '_last_handshake_at',
|
"_socket",
|
||||||
'_ping_nonce', '_last_ping_at', '_last_ping_rtt')
|
"_version",
|
||||||
|
"_ready",
|
||||||
|
"_incoming",
|
||||||
|
"_connected_at",
|
||||||
|
"_last_received_at",
|
||||||
|
"_bytes_sent",
|
||||||
|
"_bytes_received",
|
||||||
|
"_receiving_length",
|
||||||
|
"_receiving_buffer",
|
||||||
|
"_recv_messages",
|
||||||
|
"_misbehaving_score",
|
||||||
|
"_ke",
|
||||||
|
"_km",
|
||||||
|
"_dir",
|
||||||
|
"_sent_nonce",
|
||||||
|
"_recv_nonce",
|
||||||
|
"_last_handshake_at",
|
||||||
|
"_ping_nonce",
|
||||||
|
"_last_ping_at",
|
||||||
|
"_last_ping_rtt",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, address, socket, pubkey):
|
def __init__(self, address, socket, pubkey):
|
||||||
self._mx = threading.Lock()
|
self._mx = threading.Lock()
|
||||||
@@ -141,14 +164,16 @@ def listen_thread(cls):
|
|||||||
max_bytes = 0x10000
|
max_bytes = 0x10000
|
||||||
while cls._running:
|
while cls._running:
|
||||||
# logging.info('[rm] network loop %d', cls._running)
|
# logging.info('[rm] network loop %d', cls._running)
|
||||||
readable, writable, errored = select.select(cls._read_sockets, cls._write_sockets, cls._error_sockets, timeout)
|
readable, writable, errored = select.select(
|
||||||
|
cls._read_sockets, cls._write_sockets, cls._error_sockets, timeout
|
||||||
|
)
|
||||||
cls._mx.acquire()
|
cls._mx.acquire()
|
||||||
try:
|
try:
|
||||||
disconnected_peers = []
|
disconnected_peers = []
|
||||||
for s in readable:
|
for s in readable:
|
||||||
if s == cls._socket:
|
if s == cls._socket:
|
||||||
peer_socket, address = cls._socket.accept()
|
peer_socket, address = cls._socket.accept()
|
||||||
logging.info('Connection from %s', address)
|
logging.info("Connection from %s", address)
|
||||||
new_peer = Peer(address, peer_socket, None)
|
new_peer = Peer(address, peer_socket, None)
|
||||||
new_peer._incoming = True
|
new_peer._incoming = True
|
||||||
cls._peers.append(new_peer)
|
cls._peers.append(new_peer)
|
||||||
@@ -161,11 +186,11 @@ def listen_thread(cls):
|
|||||||
bytes_recv = s.recv(max_bytes, socket.MSG_DONTWAIT)
|
bytes_recv = s.recv(max_bytes, socket.MSG_DONTWAIT)
|
||||||
except socket.error as se:
|
except socket.error as se:
|
||||||
if se.args[0] not in (socket.EWOULDBLOCK,):
|
if se.args[0] not in (socket.EWOULDBLOCK,):
|
||||||
logging.error('Receive error %s', str(se))
|
logging.error("Receive error %s", str(se))
|
||||||
disconnected_peers.append(peer)
|
disconnected_peers.append(peer)
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error('Receive error %s', str(e))
|
logging.error("Receive error %s", str(e))
|
||||||
disconnected_peers.append(peer)
|
disconnected_peers.append(peer)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -175,7 +200,7 @@ def listen_thread(cls):
|
|||||||
cls.receive_bytes(peer, bytes_recv)
|
cls.receive_bytes(peer, bytes_recv)
|
||||||
|
|
||||||
for s in errored:
|
for s in errored:
|
||||||
logging.warning('Socket error')
|
logging.warning("Socket error")
|
||||||
|
|
||||||
for peer in disconnected_peers:
|
for peer in disconnected_peers:
|
||||||
cls.disconnect(peer)
|
cls.disconnect(peer)
|
||||||
@@ -193,7 +218,9 @@ def msg_thread(cls):
|
|||||||
try:
|
try:
|
||||||
now_us = time.time_ns() // 1000
|
now_us = time.time_ns() // 1000
|
||||||
if peer._ready is True:
|
if peer._ready is True:
|
||||||
if now_us - peer._last_ping_at >= 5000000: # 5 seconds TODO: Make variable
|
if (
|
||||||
|
now_us - peer._last_ping_at >= 5000000
|
||||||
|
): # 5 seconds TODO: Make variable
|
||||||
cls.send_ping(peer)
|
cls.send_ping(peer)
|
||||||
msg = peer._recv_messages.get(False)
|
msg = peer._recv_messages.get(False)
|
||||||
cls.process_message(peer, msg)
|
cls.process_message(peer, msg)
|
||||||
@@ -201,7 +228,7 @@ def msg_thread(cls):
|
|||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning('process message error %s', str(e))
|
logging.warning("process message error %s", str(e))
|
||||||
if cls._sc.debug:
|
if cls._sc.debug:
|
||||||
logging.error(traceback.format_exc())
|
logging.error(traceback.format_exc())
|
||||||
|
|
||||||
@@ -211,9 +238,24 @@ def msg_thread(cls):
|
|||||||
|
|
||||||
class Network:
|
class Network:
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'_p2p_host', '_p2p_port', '_network_key', '_network_pubkey',
|
"_p2p_host",
|
||||||
'_sc', '_peers', '_max_connections', '_running', '_network_thread', '_msg_thread',
|
"_p2p_port",
|
||||||
'_mx', '_socket', '_read_sockets', '_write_sockets', '_error_sockets', '_csprng', '_seen_ephem_keys')
|
"_network_key",
|
||||||
|
"_network_pubkey",
|
||||||
|
"_sc",
|
||||||
|
"_peers",
|
||||||
|
"_max_connections",
|
||||||
|
"_running",
|
||||||
|
"_network_thread",
|
||||||
|
"_msg_thread",
|
||||||
|
"_mx",
|
||||||
|
"_socket",
|
||||||
|
"_read_sockets",
|
||||||
|
"_write_sockets",
|
||||||
|
"_error_sockets",
|
||||||
|
"_csprng",
|
||||||
|
"_seen_ephem_keys",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, p2p_host, p2p_port, network_key, swap_client):
|
def __init__(self, p2p_host, p2p_port, network_key, swap_client):
|
||||||
self._p2p_host = p2p_host
|
self._p2p_host = p2p_host
|
||||||
@@ -278,7 +320,13 @@ class Network:
|
|||||||
self._mx.release()
|
self._mx.release()
|
||||||
|
|
||||||
def add_connection(self, host, port, peer_pubkey):
|
def add_connection(self, host, port, peer_pubkey):
|
||||||
self._sc.log.info('Connecting from %s to %s at %s %d', self._network_pubkey.hex(), peer_pubkey.hex(), host, port)
|
self._sc.log.info(
|
||||||
|
"Connecting from %s to %s at %s %d",
|
||||||
|
self._network_pubkey.hex(),
|
||||||
|
peer_pubkey.hex(),
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
)
|
||||||
self._mx.acquire()
|
self._mx.acquire()
|
||||||
try:
|
try:
|
||||||
address = (host, port)
|
address = (host, port)
|
||||||
@@ -294,7 +342,7 @@ class Network:
|
|||||||
self.send_handshake(peer)
|
self.send_handshake(peer)
|
||||||
|
|
||||||
def disconnect(self, peer):
|
def disconnect(self, peer):
|
||||||
self._sc.log.info('Closing peer socket %s', peer._address)
|
self._sc.log.info("Closing peer socket %s", peer._address)
|
||||||
self._read_sockets.pop(self._read_sockets.index(peer._socket))
|
self._read_sockets.pop(self._read_sockets.index(peer._socket))
|
||||||
self._error_sockets.pop(self._error_sockets.index(peer._socket))
|
self._error_sockets.pop(self._error_sockets.index(peer._socket))
|
||||||
peer.close()
|
peer.close()
|
||||||
@@ -305,7 +353,11 @@ class Network:
|
|||||||
|
|
||||||
used = self._seen_ephem_keys.get(ephem_pk)
|
used = self._seen_ephem_keys.get(ephem_pk)
|
||||||
if used:
|
if used:
|
||||||
raise ValueError('Handshake ephem_pk reused %s peer %s', 'for' if direction == 1 else 'by', used[0])
|
raise ValueError(
|
||||||
|
"Handshake ephem_pk reused %s peer %s",
|
||||||
|
"for" if direction == 1 else "by",
|
||||||
|
used[0],
|
||||||
|
)
|
||||||
|
|
||||||
self._seen_ephem_keys[ephem_pk] = (peer._address, timestamp)
|
self._seen_ephem_keys[ephem_pk] = (peer._address, timestamp)
|
||||||
|
|
||||||
@@ -313,12 +365,14 @@ class Network:
|
|||||||
self._seen_ephem_keys.popitem(last=False)
|
self._seen_ephem_keys.popitem(last=False)
|
||||||
|
|
||||||
def send_handshake(self, peer):
|
def send_handshake(self, peer):
|
||||||
self._sc.log.debug('send_handshake %s', peer._address)
|
self._sc.log.debug("send_handshake %s", peer._address)
|
||||||
peer._mx.acquire()
|
peer._mx.acquire()
|
||||||
try:
|
try:
|
||||||
# TODO: Drain peer._recv_messages
|
# TODO: Drain peer._recv_messages
|
||||||
if not peer._recv_messages.empty():
|
if not peer._recv_messages.empty():
|
||||||
self._sc.log.warning('send_handshake %s - Receive queue dumped.', peer._address)
|
self._sc.log.warning(
|
||||||
|
"send_handshake %s - Receive queue dumped.", peer._address
|
||||||
|
)
|
||||||
while not peer._recv_messages.empty():
|
while not peer._recv_messages.empty():
|
||||||
peer._recv_messages.get(False)
|
peer._recv_messages.get(False)
|
||||||
|
|
||||||
@@ -332,7 +386,7 @@ class Network:
|
|||||||
|
|
||||||
ss = k.ecdh(peer._pubkey)
|
ss = k.ecdh(peer._pubkey)
|
||||||
|
|
||||||
hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, 'big')).digest()
|
hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, "big")).digest()
|
||||||
peer._ke = hashed[:32]
|
peer._ke = hashed[:32]
|
||||||
peer._km = hashed[32:]
|
peer._km = hashed[32:]
|
||||||
|
|
||||||
@@ -361,11 +415,13 @@ class Network:
|
|||||||
peer._mx.release()
|
peer._mx.release()
|
||||||
|
|
||||||
def process_handshake(self, peer, msg_mv):
|
def process_handshake(self, peer, msg_mv):
|
||||||
self._sc.log.debug('process_handshake %s', peer._address)
|
self._sc.log.debug("process_handshake %s", peer._address)
|
||||||
|
|
||||||
# TODO: Drain peer._recv_messages
|
# TODO: Drain peer._recv_messages
|
||||||
if not peer._recv_messages.empty():
|
if not peer._recv_messages.empty():
|
||||||
self._sc.log.warning('process_handshake %s - Receive queue dumped.', peer._address)
|
self._sc.log.warning(
|
||||||
|
"process_handshake %s - Receive queue dumped.", peer._address
|
||||||
|
)
|
||||||
while not peer._recv_messages.empty():
|
while not peer._recv_messages.empty():
|
||||||
peer._recv_messages.get(False)
|
peer._recv_messages.get(False)
|
||||||
|
|
||||||
@@ -375,17 +431,19 @@ class Network:
|
|||||||
try:
|
try:
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
if now - peer._last_handshake_at < 30:
|
if now - peer._last_handshake_at < 30:
|
||||||
raise ValueError('Too many handshakes from peer %s', peer._address)
|
raise ValueError("Too many handshakes from peer %s", peer._address)
|
||||||
|
|
||||||
if abs(msg._timestamp - now) > TIMESTAMP_LEEWAY:
|
if abs(msg._timestamp - now) > TIMESTAMP_LEEWAY:
|
||||||
raise ValueError('Bad handshake timestamp from peer %s', peer._address)
|
raise ValueError("Bad handshake timestamp from peer %s", peer._address)
|
||||||
|
|
||||||
self.check_handshake_ephem_key(peer, msg._timestamp, msg._ephem_pk, direction=2)
|
self.check_handshake_ephem_key(
|
||||||
|
peer, msg._timestamp, msg._ephem_pk, direction=2
|
||||||
|
)
|
||||||
|
|
||||||
nk = PrivateKey(self._network_key)
|
nk = PrivateKey(self._network_key)
|
||||||
ss = nk.ecdh(msg._ephem_pk)
|
ss = nk.ecdh(msg._ephem_pk)
|
||||||
|
|
||||||
hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, 'big')).digest()
|
hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, "big")).digest()
|
||||||
peer._ke = hashed[:32]
|
peer._ke = hashed[:32]
|
||||||
peer._km = hashed[32:]
|
peer._km = hashed[32:]
|
||||||
|
|
||||||
@@ -395,7 +453,9 @@ class Network:
|
|||||||
aad += nonce
|
aad += nonce
|
||||||
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
||||||
cipher.update(aad)
|
cipher.update(aad)
|
||||||
plaintext = cipher.decrypt_and_verify(msg._ct, msg._mac) # Will raise error if mac doesn't match
|
plaintext = cipher.decrypt_and_verify(
|
||||||
|
msg._ct, msg._mac
|
||||||
|
) # Will raise error if mac doesn't match
|
||||||
|
|
||||||
peer._version = plaintext[:6]
|
peer._version = plaintext[:6]
|
||||||
sig = plaintext[6:]
|
sig = plaintext[6:]
|
||||||
@@ -414,7 +474,7 @@ class Network:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# TODO: misbehaving
|
# TODO: misbehaving
|
||||||
self._sc.log.debug('[rm] process_handshake %s', str(e))
|
self._sc.log.debug("[rm] process_handshake %s", str(e))
|
||||||
|
|
||||||
def process_ping(self, peer, msg_mv):
|
def process_ping(self, peer, msg_mv):
|
||||||
nonce = peer._recv_nonce[:24]
|
nonce = peer._recv_nonce[:24]
|
||||||
@@ -426,14 +486,18 @@ class Network:
|
|||||||
mac = msg_mv[-16:]
|
mac = msg_mv[-16:]
|
||||||
plaintext = cipher.decrypt_and_verify(msg_mv[2:-16], mac)
|
plaintext = cipher.decrypt_and_verify(msg_mv[2:-16], mac)
|
||||||
|
|
||||||
ping_nonce = int.from_bytes(plaintext[:4], 'big')
|
ping_nonce = int.from_bytes(plaintext[:4], "big")
|
||||||
# Version is added to a ping following a handshake message
|
# Version is added to a ping following a handshake message
|
||||||
if len(plaintext) >= 10:
|
if len(plaintext) >= 10:
|
||||||
peer._ready = True
|
peer._ready = True
|
||||||
version = plaintext[4:10]
|
version = plaintext[4:10]
|
||||||
if peer._version is None:
|
if peer._version is None:
|
||||||
peer._version = version
|
peer._version = version
|
||||||
self._sc.log.debug('Set version from ping %s, %s', peer._pubkey.hex(), peer._version.hex())
|
self._sc.log.debug(
|
||||||
|
"Set version from ping %s, %s",
|
||||||
|
peer._pubkey.hex(),
|
||||||
|
peer._version.hex(),
|
||||||
|
)
|
||||||
|
|
||||||
peer._recv_nonce = hashlib.sha256(nonce + mac).digest()
|
peer._recv_nonce = hashlib.sha256(nonce + mac).digest()
|
||||||
|
|
||||||
@@ -449,26 +513,26 @@ class Network:
|
|||||||
mac = msg_mv[-16:]
|
mac = msg_mv[-16:]
|
||||||
plaintext = cipher.decrypt_and_verify(msg_mv[2:-16], mac)
|
plaintext = cipher.decrypt_and_verify(msg_mv[2:-16], mac)
|
||||||
|
|
||||||
pong_nonce = int.from_bytes(plaintext[:4], 'big')
|
pong_nonce = int.from_bytes(plaintext[:4], "big")
|
||||||
|
|
||||||
if pong_nonce == peer._ping_nonce:
|
if pong_nonce == peer._ping_nonce:
|
||||||
peer._last_ping_rtt = (time.time_ns() // 1000) - peer._last_ping_at
|
peer._last_ping_rtt = (time.time_ns() // 1000) - peer._last_ping_at
|
||||||
else:
|
else:
|
||||||
self._sc.log.debug('Pong received out of order %s', peer._address)
|
self._sc.log.debug("Pong received out of order %s", peer._address)
|
||||||
|
|
||||||
peer._recv_nonce = hashlib.sha256(nonce + mac).digest()
|
peer._recv_nonce = hashlib.sha256(nonce + mac).digest()
|
||||||
|
|
||||||
def send_ping(self, peer):
|
def send_ping(self, peer):
|
||||||
ping_nonce = random.getrandbits(32)
|
ping_nonce = random.getrandbits(32)
|
||||||
|
|
||||||
msg_bytes = int(NetMessageTypes.PING).to_bytes(2, 'big')
|
msg_bytes = int(NetMessageTypes.PING).to_bytes(2, "big")
|
||||||
nonce = peer._sent_nonce[:24]
|
nonce = peer._sent_nonce[:24]
|
||||||
|
|
||||||
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
||||||
cipher.update(msg_bytes)
|
cipher.update(msg_bytes)
|
||||||
cipher.update(nonce)
|
cipher.update(nonce)
|
||||||
|
|
||||||
payload = ping_nonce.to_bytes(4, 'big')
|
payload = ping_nonce.to_bytes(4, "big")
|
||||||
if peer._last_ping_at == 0:
|
if peer._last_ping_at == 0:
|
||||||
payload += self._sc._version
|
payload += self._sc._version
|
||||||
ct, mac = cipher.encrypt_and_digest(payload)
|
ct, mac = cipher.encrypt_and_digest(payload)
|
||||||
@@ -483,14 +547,14 @@ class Network:
|
|||||||
self.send_msg(peer, msg_bytes)
|
self.send_msg(peer, msg_bytes)
|
||||||
|
|
||||||
def send_pong(self, peer, ping_nonce):
|
def send_pong(self, peer, ping_nonce):
|
||||||
msg_bytes = int(NetMessageTypes.PONG).to_bytes(2, 'big')
|
msg_bytes = int(NetMessageTypes.PONG).to_bytes(2, "big")
|
||||||
nonce = peer._sent_nonce[:24]
|
nonce = peer._sent_nonce[:24]
|
||||||
|
|
||||||
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce)
|
||||||
cipher.update(msg_bytes)
|
cipher.update(msg_bytes)
|
||||||
cipher.update(nonce)
|
cipher.update(nonce)
|
||||||
|
|
||||||
payload = ping_nonce.to_bytes(4, 'big')
|
payload = ping_nonce.to_bytes(4, "big")
|
||||||
ct, mac = cipher.encrypt_and_digest(payload)
|
ct, mac = cipher.encrypt_and_digest(payload)
|
||||||
msg_bytes += ct + mac
|
msg_bytes += ct + mac
|
||||||
|
|
||||||
@@ -502,19 +566,21 @@ class Network:
|
|||||||
msg_encoded = msg if isinstance(msg, bytes) else msg.encode()
|
msg_encoded = msg if isinstance(msg, bytes) else msg.encode()
|
||||||
len_encoded = len(msg_encoded)
|
len_encoded = len(msg_encoded)
|
||||||
|
|
||||||
msg_packed = bytearray(MSG_START_TOKEN) + len_encoded.to_bytes(4, 'big') + msg_encoded
|
msg_packed = (
|
||||||
|
bytearray(MSG_START_TOKEN) + len_encoded.to_bytes(4, "big") + msg_encoded
|
||||||
|
)
|
||||||
peer._socket.sendall(msg_packed)
|
peer._socket.sendall(msg_packed)
|
||||||
|
|
||||||
peer._bytes_sent += len_encoded
|
peer._bytes_sent += len_encoded
|
||||||
|
|
||||||
def process_message(self, peer, msg_bytes):
|
def process_message(self, peer, msg_bytes):
|
||||||
logging.info('[rm] process_message %s len %d', peer._address, len(msg_bytes))
|
logging.info("[rm] process_message %s len %d", peer._address, len(msg_bytes))
|
||||||
|
|
||||||
peer._mx.acquire()
|
peer._mx.acquire()
|
||||||
try:
|
try:
|
||||||
mv = memoryview(msg_bytes)
|
mv = memoryview(msg_bytes)
|
||||||
o = 0
|
o = 0
|
||||||
msg_type = int.from_bytes(mv[o: o + 2], 'big')
|
msg_type = int.from_bytes(mv[o : o + 2], "big")
|
||||||
if msg_type == NetMessageTypes.HANDSHAKE:
|
if msg_type == NetMessageTypes.HANDSHAKE:
|
||||||
self.process_handshake(peer, mv)
|
self.process_handshake(peer, mv)
|
||||||
elif msg_type == NetMessageTypes.PING:
|
elif msg_type == NetMessageTypes.PING:
|
||||||
@@ -522,7 +588,7 @@ class Network:
|
|||||||
elif msg_type == NetMessageTypes.PONG:
|
elif msg_type == NetMessageTypes.PONG:
|
||||||
self.process_pong(peer, mv)
|
self.process_pong(peer, mv)
|
||||||
else:
|
else:
|
||||||
self._sc.log.debug('Unknown message type %d', msg_type)
|
self._sc.log.debug("Unknown message type %d", msg_type)
|
||||||
finally:
|
finally:
|
||||||
peer._mx.release()
|
peer._mx.release()
|
||||||
|
|
||||||
@@ -533,7 +599,6 @@ class Network:
|
|||||||
peer._last_received_at = time.time()
|
peer._last_received_at = time.time()
|
||||||
peer._bytes_received += len_received
|
peer._bytes_received += len_received
|
||||||
|
|
||||||
invalid_msg = False
|
|
||||||
mv = memoryview(bytes_recv)
|
mv = memoryview(bytes_recv)
|
||||||
|
|
||||||
o = 0
|
o = 0
|
||||||
@@ -541,32 +606,32 @@ class Network:
|
|||||||
while o < len_received:
|
while o < len_received:
|
||||||
if peer._receiving_length == 0:
|
if peer._receiving_length == 0:
|
||||||
if len(bytes_recv) < MSG_HEADER_LEN:
|
if len(bytes_recv) < MSG_HEADER_LEN:
|
||||||
raise ValueError('Msg too short')
|
raise ValueError("Msg too short")
|
||||||
|
|
||||||
if mv[o : o + 2] != MSG_START_TOKEN:
|
if mv[o : o + 2] != MSG_START_TOKEN:
|
||||||
raise ValueError('Invalid start token')
|
raise ValueError("Invalid start token")
|
||||||
o += 2
|
o += 2
|
||||||
|
|
||||||
msg_len = int.from_bytes(mv[o: o + 4], 'big')
|
msg_len = int.from_bytes(mv[o : o + 4], "big")
|
||||||
o += 4
|
o += 4
|
||||||
if msg_len < 2 or msg_len > MSG_MAX_SIZE:
|
if msg_len < 2 or msg_len > MSG_MAX_SIZE:
|
||||||
raise ValueError('Invalid data length')
|
raise ValueError("Invalid data length")
|
||||||
|
|
||||||
# Precheck msg_type
|
# Precheck msg_type
|
||||||
msg_type = int.from_bytes(mv[o: o + 2], 'big')
|
msg_type = int.from_bytes(mv[o : o + 2], "big")
|
||||||
# o += 2 # Don't inc offset, msg includes type
|
# o += 2 # Don't inc offset, msg includes type
|
||||||
if not NetMessageTypes.has_value(msg_type):
|
if not NetMessageTypes.has_value(msg_type):
|
||||||
raise ValueError('Invalid msg type')
|
raise ValueError("Invalid msg type")
|
||||||
|
|
||||||
peer._receiving_length = msg_len
|
peer._receiving_length = msg_len
|
||||||
len_pkt = (len_received - o)
|
len_pkt = len_received - o
|
||||||
nc = msg_len if len_pkt > msg_len else len_pkt
|
nc = msg_len if len_pkt > msg_len else len_pkt
|
||||||
|
|
||||||
peer._receiving_buffer = mv[o : o + nc]
|
peer._receiving_buffer = mv[o : o + nc]
|
||||||
o += nc
|
o += nc
|
||||||
else:
|
else:
|
||||||
len_to_go = peer._receiving_length - len(peer._receiving_buffer)
|
len_to_go = peer._receiving_length - len(peer._receiving_buffer)
|
||||||
len_pkt = (len_received - o)
|
len_pkt = len_received - o
|
||||||
nc = len_to_go if len_pkt > len_to_go else len_pkt
|
nc = len_to_go if len_pkt > len_to_go else len_pkt
|
||||||
peer._receiving_buffer = mv[o : o + nc]
|
peer._receiving_buffer = mv[o : o + nc]
|
||||||
o += nc
|
o += nc
|
||||||
@@ -576,11 +641,13 @@ class Network:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self._sc.debug:
|
if self._sc.debug:
|
||||||
self._sc.log.error('Invalid message received from %s %s', peer._address, str(e))
|
self._sc.log.error(
|
||||||
|
"Invalid message received from %s %s", peer._address, str(e)
|
||||||
|
)
|
||||||
# TODO: misbehaving
|
# TODO: misbehaving
|
||||||
|
|
||||||
def test_onion(self, path):
|
def test_onion(self, path):
|
||||||
self._sc.log.debug('test_onion packet')
|
self._sc.log.debug("test_onion packet")
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
rv = {}
|
rv = {}
|
||||||
@@ -589,14 +656,14 @@ class Network:
|
|||||||
with self._mx:
|
with self._mx:
|
||||||
for peer in self._peers:
|
for peer in self._peers:
|
||||||
peer_info = {
|
peer_info = {
|
||||||
'pubkey': 'Unknown' if not peer._pubkey else peer._pubkey.hex(),
|
"pubkey": "Unknown" if not peer._pubkey else peer._pubkey.hex(),
|
||||||
'address': '{}:{}'.format(peer._address[0], peer._address[1]),
|
"address": "{}:{}".format(peer._address[0], peer._address[1]),
|
||||||
'bytessent': peer._bytes_sent,
|
"bytessent": peer._bytes_sent,
|
||||||
'bytesrecv': peer._bytes_received,
|
"bytesrecv": peer._bytes_received,
|
||||||
'ready': peer._ready,
|
"ready": peer._ready,
|
||||||
'incoming': peer._incoming,
|
"incoming": peer._incoming,
|
||||||
}
|
}
|
||||||
peers.append(peer_info)
|
peers.append(peer_info)
|
||||||
|
|
||||||
rv['peers'] = peers
|
rv["peers"] = peers
|
||||||
return rv
|
return rv
|
||||||
|
|||||||
1257
basicswap/pgp/keys/bitcoin_laanwj.pgp
Normal file
166
basicswap/pgp/keys/dash_pasta.pgp
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
t8FIucvlOdt4EqKaSmoBQZk0Aj/N5q4=
|
||||||
|
=vjZr
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
31
basicswap/pgp/keys/dogecoin_patricklodder.pgp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQENBF8V/EkBCAC8YTo6YJLNY0To+25b+dcSRcMCo/g9TJlraoJagO9Hr0Njbryg
|
||||||
|
jG5iptxi6UjDD+8xPK7YYRhaKyzJq1yTjGe5u5WEEtMfNaiVgA6dSEOXTdH4xT6q
|
||||||
|
v3VundebzZ7TFue7kj7fzEh7t9x2k5+RI2RvOs26ANEBKgJliQIZDXKOLcQuW7k9
|
||||||
|
9pWvqMWqRyn8WVGNf/UGBoFDcXQ1wo3h6m/LMJIO5L2IGlQWPmc8WT3uHJ/X/5Ln
|
||||||
|
slQ1ml7h+JjNwN0rAY/ZaJHSEi2y0RtLRzISP0EsA6EbqvJNGI8jqs5rpImgUn9U
|
||||||
|
8Q8Xz6hLPAiVTmteF63LlKo03wRcH8d/FVSvABEBAAG0N1BhdHJpY2sgTG9kZGVy
|
||||||
|
IDxwYXRyaWNrbG9kZGVyQHVzZXJzLm5vcmVwbHkuZ2l0aHViLmNvbT6JAVQEEwEI
|
||||||
|
AD4CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTcbvSov58bHk3h7lItOjRb
|
||||||
|
mNDcHwUCYtNqvwUJB3/VdgAKCRAtOjRbmNDcH+sVB/9jGPwrd1Om6L3ALzkZniR7
|
||||||
|
ODYFN4m8MRC4LPH2Ngt1Ea3/5DA68hEzQVGAFF+m7i7ZH9bmTvGB9R+qqF9WLTRc
|
||||||
|
aoO0XvYI8YrRLuhZFazafsLFRD5/c6QfpkBAjiDuxNIjEg2i+nY3avraxicKQKBY
|
||||||
|
PWWY0TFbz8K+CgIBh8Dnv7lqcxCFWHit/KHHjGAOvIPD5sLtv42dYk4TBEff4MVK
|
||||||
|
CzuCQtU8viy5doQPYHwfNADpOguskiNtFZmG2iPwgIE2tzHpLG2kidzZvJbHDcXY
|
||||||
|
XP13FnLvONf2bkS11gZSRm8pa6uay8/KfBNlCeMOYQDVoCuBbD5/2MwuV6o6OfSI
|
||||||
|
uQENBF8V/EkBCADN8eWUf0OtQdthNoWhRgotz/EzLI9r3sVv2SqbA++rHW9TC7mB
|
||||||
|
Wl/3e5emXWgKI1EK1Poz5HeKnL3SRx3xizgBTK6+RNQK6svvaLwcx06y8pZP9RqX
|
||||||
|
jLaRR67fXZCL+ulPtTcbt/JwlaTaokwWsgfy3UZRcK33llLbvWFjht2OGfx8B6Z9
|
||||||
|
UFRxW4sP0HuE3RrnMATGymWvOZlwYDr73HltksnOEFkz4lVP5VK9kdbndQjIB3Cf
|
||||||
|
zw/waTqjX+xXjJsFMYZhEDARhP5BQIoQvEv8KRtptNoLJGFZ9RGf+fIHiar2GAZL
|
||||||
|
4WZbZ0IuGLj419TkgvsUkI83Bx97DkS5Xa+jABEBAAGJATwEGAEIACYCGwwWIQTc
|
||||||
|
bvSov58bHk3h7lItOjRbmNDcHwUCYtNq0AUJB3/VhwAKCRAtOjRbmNDcH8cfB/4q
|
||||||
|
Puoir46sAGHBJt4TVe+R5ErVmGfGVUc3n6svguJnRMTAi1gpb6EapjdR9gUx+3Ja
|
||||||
|
wUE1keJuw5xeFi2JGp/XHt+8LAhsRAaLA4YViho8KL3yjzARvqrkYfl+FuO6kZIj
|
||||||
|
FEPJjRI1hOx5pWtPa3L3GZOexYDhRVdIJDci3gbFmU8HjgFx0G50zAysGR4DLVXj
|
||||||
|
FQBPvt4asUTdx30HU/pxWqFEzAeJPOVyjoxotdsMcIYXVBDhte5eADJ4OSMmc7k3
|
||||||
|
k46yHnbD4wyqqGtWqxHitTrl2U+M5MO5rlOZpGtIMtHz186OyMySZ5Gc886vPlOG
|
||||||
|
XgtNHT7E4rDrhySwy6Yk
|
||||||
|
=DQYN
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
41
basicswap/pgp/keys/dogecoin_xanimo.pgp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
tS35OCTSJzNMoXtTZqCkDLc9
|
||||||
|
=Z8rt
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
161
basicswap/pgp/keys/nicolasdorier.asc
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Comment: https://keybase.io/nicolasdorier
|
||||||
|
Version: Keybase Go 2.6.0 (linux)
|
||||||
|
|
||||||
|
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
|
||||||
|
=sNb2
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Comment: https://keybase.io/nicolasdorier
|
||||||
|
Version: Keybase Go 5.0.0 (windows)
|
||||||
|
|
||||||
|
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
|
||||||
|
=ut7t
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Comment: https://keybase.io/nicolasdorier
|
||||||
|
Version: Keybase Go 5.0.0 (windows)
|
||||||
|
|
||||||
|
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
|
||||||
|
=n82A
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQIzBAABCAAdFiEEjlF9wS7BzDf2QjqKE/E2UcnPDWsFAmbbRWEACgkQE/E2UcnP
|
||||||
|
DWsVcxAArcHDK8mzY9RkiWuaW9l3PItU/n6NH7XCPqchnkum3IXHWUn/hU3Dzq8M
|
||||||
|
pZCEksI4jm3tGVxz+RvjWHCQBk0e40LOmYfUrAXFiyrchw0VQKbzF81q0HL7jUB3
|
||||||
|
0GlwvSX7o4B2TNjEAcD6x6ztwHLZKEAFwHBtUlSxkqAPkTzkrxMBptKUwhGj3uWW
|
||||||
|
p9NDsRKAmzitpg+H/+Puh5HcC05bqhsMZ2h3iyswmrP9DJvsKjAYHEwLECzoFn72
|
||||||
|
On+AkV/msvJSkp4O8gr2j4IZ/Mm/zOe1PAq7UdVMWGdxfjn37Ci0adY7ZJd/9tsk
|
||||||
|
VcaHRzJ91iTItol0FFX+ytOxKNyMq311EWaWR2lecx/cagt8nA4oELrKGwTy4dDV
|
||||||
|
kSXUF8PYXtFLAPhow42ARQdk4bgOolwT4AYjHMUoK25RiYbY4dFyuiOv/OHg6Wvs
|
||||||
|
Ostfpy+VKD9qi3NMBf5i0QoxtJZQuWA6PaR7W+idLRgXA6LJB4OEtd5WmCvG10zX
|
||||||
|
hkToSGPN6AMPSta1BIoFnDbTBYQvsslxwSWM4wmQgJbkqSYU4vg8JRJ58spUXn5/
|
||||||
|
DIRU8slQuIvKek9D67aahpdA9W0csoOhHGnnWk/JS/Hyzk6v9b3N3L82ct8tzhH6
|
||||||
|
MhnH7vVrRDxGAsWDeGkCAH2/KxSaafqWTjXoSDrftPueI07BeAY=
|
||||||
|
=TH7+
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQIzBAABCAAdFiEEjlF9wS7BzDf2QjqKE/E2UcnPDWsFAmeP/40ACgkQE/E2UcnP
|
||||||
|
DWuraw/9HCuAZG+D6xSLWmjA9Dtj9OZMEOIxqvxw+1e2KQ5ek4d1waL63NWFQfMi
|
||||||
|
fDlKKeFbZoL6Dfjbx0GoUJKTfrIVKog6DlVzIi5PuUwPOCBFuLl0g5kHlC20jbPw
|
||||||
|
nu7T6fj6/oD/lqo0rzFDkbsX7Fk4GGC7rYLKfdtYhDgMq9ro7QhSxAOJanRyqzXL
|
||||||
|
dvPNxlyksOyttJLSAZI9BOkrpTWoyb3asOli5oHgdcheHd/2fjby69huS3UWEjdO
|
||||||
|
9Bm73UFlxF2hxCTc2Fqvvb3SBDmNCLlFM0f+DDJNMJGUQViVCar0YRw3R+/NBo83
|
||||||
|
ptutp3bpabHijQFEEpIx/19nh9RQMJjaHHHqdPcTeg8bU/Yeq36TI7gsCenK0mQT
|
||||||
|
75MscvJAG0enoKVrTZez5ner9ZwLOevAKzRe4huRJZZjM8gM6sb2OKslJLqTxEVt
|
||||||
|
G3b8BLB9IUAxCeyuvGSG/3RV3MgZLnLy5MLYjh72+Kmo6HpuajJwPuvUck5ZYcGE
|
||||||
|
jjeRFZmqZj0FtCrcfStau/0liyAxU5k/43RwMvujO1uTTgOVHw1QhhMEkZ9bYhhO
|
||||||
|
JgeCEkwL1Bjjved1NSySjZbt2sFbG89as14ezHxgc4HaujJ6bGkINnkPOPWM1tk4
|
||||||
|
DjjEO/0PY9i0m/ivQUXf5ZPSnlkAR8x6Ve2S2MvQd7nFoS/YfLs=
|
||||||
|
=0pTn
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
21
basicswap/pgp/sigs/utxo-snapshot-bitcoin-mainnet-hashes.asc
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-----BEGIN PGP SIGNED MESSAGE-----
|
||||||
|
Hash: SHA256
|
||||||
|
|
||||||
|
725a049bc5a9fd60b05bba4d4825d35115d99f05ab5b7716d4507c295d05172d utxo-snapshot-bitcoin-mainnet-820852.tar
|
||||||
|
744c42885df700513331a978b289d9c9d5b27e0cf1147f2f5a287b4492ff940c utxo-snapshot-bitcoin-mainnet-867690.tar
|
||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQIzBAEBCAAdFiEEjlF9wS7BzDf2QjqKE/E2UcnPDWsFAmedUAYACgkQE/E2UcnP
|
||||||
|
DWs1Vw/+P3CGP9LLVv2deNocBFunUz+7aDZsQiykSI8ws50ssJ5PsAg5VSl4CbCl
|
||||||
|
owWOdQVJiDUh7daP0jr+bt3X2FY5ORBb1TGlvfCHE+vLfEFDnTpLXouSCclP0cv8
|
||||||
|
Ci8zQFKSI5Pf6uSMpALgQZxBgNU/0IegAQbpuJI4nrQXTKHJcMqtw1LtnmcreESO
|
||||||
|
MsSiGCXnC1R+xGQjptfvbzXaQVrin7ctYA9zjN4CGbjNChzr+ywT8dht2RKoLYyP
|
||||||
|
OrEys7d8EIaw/ktRvRmyk6O7KmnvUhf0uuFlDq+eTiBIpQoUEovCow1YYKaWkIRB
|
||||||
|
r4JBJJ34AB+XC2hgi5jpJNub/wKgVBm0iy79zZOSILP3ymbn3iJGg4ifUF0YeZCU
|
||||||
|
ufYkYi3iTJDpwYr0tylZmBiwsWNcbUhB+WTNX7ogCW70ZuhrF0PJQRPmhI34vsE/
|
||||||
|
qg3n0/hNNsypy0epRd33KSOvrSmaoTKLtCax9Osnt+F+yTYjD5EPqkQuzlJl+fDe
|
||||||
|
VvjWO5XHuaRvzijBrJQz6r5V4e/0ioNa8FTRqWmMTO1wHmxF5glpozyKycv9+bsB
|
||||||
|
IL9F1IQjhPkSVI7Hw8bsURpfH4mV+9eZJJDIvBf1/0gDctsBdsI5+5jxZjup769Q
|
||||||
|
AmMsGeZoplm/eUofQ9hItWcVitPhisDmC3wDR71UKM0b9FF6IUY=
|
||||||
|
=YUjt
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
@@ -16,19 +16,26 @@ class ProtocolInterface:
|
|||||||
swap_type = None
|
swap_type = None
|
||||||
|
|
||||||
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
||||||
raise ValueError('base class')
|
raise ValueError("base class")
|
||||||
|
|
||||||
def getMockScript(self) -> bytearray:
|
def getMockScript(self) -> bytearray:
|
||||||
return bytearray([
|
return bytearray([OpCodes.OP_RETURN, OpCodes.OP_1])
|
||||||
OpCodes.OP_RETURN, OpCodes.OP_1])
|
|
||||||
|
|
||||||
def getMockScriptScriptPubkey(self, ci) -> bytearray:
|
def getMockScriptScriptPubkey(self, ci) -> bytearray:
|
||||||
script = self.getMockScript()
|
script = self.getMockScript()
|
||||||
return ci.getScriptDest(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
|
return (
|
||||||
|
ci.getScriptDest(script)
|
||||||
|
if ci._use_segwit
|
||||||
|
else ci.get_p2sh_script_pubkey(script)
|
||||||
|
)
|
||||||
|
|
||||||
def getMockAddrTo(self, ci):
|
def getMockAddrTo(self, ci):
|
||||||
script = self.getMockScript()
|
script = self.getMockScript()
|
||||||
return ci.encodeScriptDest(ci.getScriptDest(script)) if ci._use_segwit else ci.encode_p2sh(script)
|
return (
|
||||||
|
ci.encodeScriptDest(ci.getScriptDest(script))
|
||||||
|
if ci._use_segwit
|
||||||
|
else ci.encode_p2sh(script)
|
||||||
|
)
|
||||||
|
|
||||||
def findMockVout(self, ci, itx_decoded):
|
def findMockVout(self, ci, itx_decoded):
|
||||||
mock_addr = self.getMockAddrTo(ci)
|
mock_addr = self.getMockAddrTo(ci)
|
||||||
|
|||||||
@@ -26,52 +26,66 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
|
|||||||
ABS_LOCK_TIME_LEEWAY = 10 * 60
|
ABS_LOCK_TIME_LEEWAY = 10 * 60
|
||||||
|
|
||||||
|
|
||||||
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256) -> bytearray:
|
def buildContractScript(
|
||||||
script = bytearray([
|
lock_val: int,
|
||||||
|
secret_hash: bytes,
|
||||||
|
pkh_redeem: bytes,
|
||||||
|
pkh_refund: bytes,
|
||||||
|
op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY,
|
||||||
|
op_hash=OpCodes.OP_SHA256,
|
||||||
|
) -> bytearray:
|
||||||
|
script = (
|
||||||
|
bytearray(
|
||||||
|
[
|
||||||
OpCodes.OP_IF,
|
OpCodes.OP_IF,
|
||||||
OpCodes.OP_SIZE,
|
OpCodes.OP_SIZE,
|
||||||
0x01, 0x20, # 32
|
0x01,
|
||||||
|
0x20, # 32
|
||||||
OpCodes.OP_EQUALVERIFY,
|
OpCodes.OP_EQUALVERIFY,
|
||||||
op_hash,
|
op_hash,
|
||||||
0x20]) \
|
0x20,
|
||||||
+ secret_hash \
|
]
|
||||||
+ bytearray([
|
)
|
||||||
OpCodes.OP_EQUALVERIFY,
|
+ secret_hash
|
||||||
OpCodes.OP_DUP,
|
+ bytearray([OpCodes.OP_EQUALVERIFY, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14])
|
||||||
OpCodes.OP_HASH160,
|
+ pkh_redeem
|
||||||
0x14]) \
|
+ bytearray(
|
||||||
+ pkh_redeem \
|
[
|
||||||
+ bytearray([OpCodes.OP_ELSE, ]) \
|
OpCodes.OP_ELSE,
|
||||||
+ SerialiseNum(lock_val) \
|
]
|
||||||
+ bytearray([
|
)
|
||||||
op_lock,
|
+ SerialiseNum(lock_val)
|
||||||
OpCodes.OP_DROP,
|
+ bytearray(
|
||||||
OpCodes.OP_DUP,
|
[op_lock, OpCodes.OP_DROP, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14]
|
||||||
OpCodes.OP_HASH160,
|
)
|
||||||
0x14]) \
|
+ pkh_refund
|
||||||
+ pkh_refund \
|
+ bytearray([OpCodes.OP_ENDIF, OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG])
|
||||||
+ bytearray([
|
)
|
||||||
OpCodes.OP_ENDIF,
|
|
||||||
OpCodes.OP_EQUALVERIFY,
|
|
||||||
OpCodes.OP_CHECKSIG])
|
|
||||||
return script
|
return script
|
||||||
|
|
||||||
|
|
||||||
def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256):
|
def verifyContractScript(
|
||||||
if script[0] != OpCodes.OP_IF or \
|
script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256
|
||||||
script[1] != OpCodes.OP_SIZE or \
|
):
|
||||||
script[2] != 0x01 or script[3] != 0x20 or \
|
if (
|
||||||
script[4] != OpCodes.OP_EQUALVERIFY or \
|
script[0] != OpCodes.OP_IF
|
||||||
script[5] != op_hash or \
|
or script[1] != OpCodes.OP_SIZE
|
||||||
script[6] != 0x20:
|
or script[2] != 0x01
|
||||||
|
or script[3] != 0x20
|
||||||
|
or script[4] != OpCodes.OP_EQUALVERIFY
|
||||||
|
or script[5] != op_hash
|
||||||
|
or script[6] != 0x20
|
||||||
|
):
|
||||||
return False, None, None, None, None
|
return False, None, None, None, None
|
||||||
o = 7
|
o = 7
|
||||||
script_hash = script[o : o + 32]
|
script_hash = script[o : o + 32]
|
||||||
o += 32
|
o += 32
|
||||||
if script[o] != OpCodes.OP_EQUALVERIFY or \
|
if (
|
||||||
script[o + 1] != OpCodes.OP_DUP or \
|
script[o] != OpCodes.OP_EQUALVERIFY
|
||||||
script[o + 2] != OpCodes.OP_HASH160 or \
|
or script[o + 1] != OpCodes.OP_DUP
|
||||||
script[o + 3] != 0x14:
|
or script[o + 2] != OpCodes.OP_HASH160
|
||||||
|
or script[o + 3] != 0x14
|
||||||
|
):
|
||||||
return False, script_hash, None, None, None
|
return False, script_hash, None, None, None
|
||||||
o += 4
|
o += 4
|
||||||
pkh_redeem = script[o : o + 20]
|
pkh_redeem = script[o : o + 20]
|
||||||
@@ -81,18 +95,22 @@ def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash
|
|||||||
o += 1
|
o += 1
|
||||||
lock_val, nb = decodeScriptNum(script, o)
|
lock_val, nb = decodeScriptNum(script, o)
|
||||||
o += nb
|
o += nb
|
||||||
if script[o] != op_lock or \
|
if (
|
||||||
script[o + 1] != OpCodes.OP_DROP or \
|
script[o] != op_lock
|
||||||
script[o + 2] != OpCodes.OP_DUP or \
|
or script[o + 1] != OpCodes.OP_DROP
|
||||||
script[o + 3] != OpCodes.OP_HASH160 or \
|
or script[o + 2] != OpCodes.OP_DUP
|
||||||
script[o + 4] != 0x14:
|
or script[o + 3] != OpCodes.OP_HASH160
|
||||||
|
or script[o + 4] != 0x14
|
||||||
|
):
|
||||||
return False, script_hash, pkh_redeem, lock_val, None
|
return False, script_hash, pkh_redeem, lock_val, None
|
||||||
o += 5
|
o += 5
|
||||||
pkh_refund = script[o : o + 20]
|
pkh_refund = script[o : o + 20]
|
||||||
o += 20
|
o += 20
|
||||||
if script[o] != OpCodes.OP_ENDIF or \
|
if (
|
||||||
script[o + 1] != OpCodes.OP_EQUALVERIFY or \
|
script[o] != OpCodes.OP_ENDIF
|
||||||
script[o + 2] != OpCodes.OP_CHECKSIG:
|
or script[o + 1] != OpCodes.OP_EQUALVERIFY
|
||||||
|
or script[o + 2] != OpCodes.OP_CHECKSIG
|
||||||
|
):
|
||||||
return False, script_hash, pkh_redeem, lock_val, pkh_refund
|
return False, script_hash, pkh_redeem, lock_val, pkh_refund
|
||||||
return True, script_hash, pkh_redeem, lock_val, pkh_refund
|
return True, script_hash, pkh_redeem, lock_val, pkh_refund
|
||||||
|
|
||||||
@@ -101,16 +119,23 @@ def extractScriptSecretHash(script):
|
|||||||
return script[7:39]
|
return script[7:39]
|
||||||
|
|
||||||
|
|
||||||
def redeemITx(self, bid_id: bytes, session):
|
def redeemITx(self, bid_id: bytes, cursor):
|
||||||
bid, offer = self.getBidAndOffer(bid_id, session)
|
bid, offer = self.getBidAndOffer(bid_id, cursor)
|
||||||
ci_from = self.ci(offer.coin_from)
|
ci_from = self.ci(offer.coin_from)
|
||||||
|
|
||||||
txn = self.createRedeemTxn(ci_from.coin_type(), bid, for_txn_type='initiate')
|
txn = self.createRedeemTxn(
|
||||||
|
ci_from.coin_type(), bid, for_txn_type="initiate", cursor=cursor
|
||||||
|
)
|
||||||
txid = ci_from.publishTx(bytes.fromhex(txn))
|
txid = ci_from.publishTx(bytes.fromhex(txn))
|
||||||
|
|
||||||
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
|
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())
|
self.log.debug(
|
||||||
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session)
|
"Submitted initiate redeem txn %s to %s chain for bid %s",
|
||||||
|
txid,
|
||||||
|
ci_from.coin_name(),
|
||||||
|
bid_id.hex(),
|
||||||
|
)
|
||||||
|
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", cursor)
|
||||||
|
|
||||||
|
|
||||||
class AtomicSwapInterface(ProtocolInterface):
|
class AtomicSwapInterface(ProtocolInterface):
|
||||||
@@ -118,13 +143,19 @@ class AtomicSwapInterface(ProtocolInterface):
|
|||||||
|
|
||||||
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
||||||
addr_to = self.getMockAddrTo(ci)
|
addr_to = self.getMockAddrTo(ci)
|
||||||
funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
|
funded_tx = ci.createRawFundedTransaction(
|
||||||
|
addr_to, amount, sub_fee, lock_unspents=False
|
||||||
|
)
|
||||||
|
|
||||||
return bytes.fromhex(funded_tx)
|
return bytes.fromhex(funded_tx)
|
||||||
|
|
||||||
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
||||||
mock_txo_script = self.getMockScriptScriptPubkey(ci)
|
mock_txo_script = self.getMockScriptScriptPubkey(ci)
|
||||||
real_txo_script = ci.getScriptDest(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script)
|
real_txo_script = (
|
||||||
|
ci.getScriptDest(script)
|
||||||
|
if ci._use_segwit
|
||||||
|
else ci.get_p2sh_script_pubkey(script)
|
||||||
|
)
|
||||||
|
|
||||||
found: int = 0
|
found: int = 0
|
||||||
ctx = ci.loadTx(mock_tx)
|
ctx = ci.loadTx(mock_tx)
|
||||||
@@ -134,9 +165,9 @@ class AtomicSwapInterface(ProtocolInterface):
|
|||||||
found += 1
|
found += 1
|
||||||
|
|
||||||
if found < 1:
|
if found < 1:
|
||||||
raise ValueError('Mocked output not found')
|
raise ValueError("Mocked output not found")
|
||||||
if found > 1:
|
if found > 1:
|
||||||
raise ValueError('Too many mocked outputs found')
|
raise ValueError("Too many mocked outputs found")
|
||||||
ctx.nLockTime = 0
|
ctx.nLockTime = 0
|
||||||
|
|
||||||
funded_tx = ctx.serialize()
|
funded_tx = ctx.serialize()
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
# 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 sqlalchemy.orm import scoped_session
|
import traceback
|
||||||
|
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
ensure,
|
ensure,
|
||||||
@@ -14,23 +15,23 @@ from basicswap.chainparams import (
|
|||||||
Coins,
|
Coins,
|
||||||
)
|
)
|
||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
|
EventLogTypes,
|
||||||
KeyTypes,
|
KeyTypes,
|
||||||
SwapTypes,
|
SwapTypes,
|
||||||
EventLogTypes,
|
TxTypes,
|
||||||
)
|
)
|
||||||
from . import ProtocolInterface
|
from . import ProtocolInterface
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import CScript, CScriptOp, OP_CHECKMULTISIG
|
||||||
CScript, CScriptOp,
|
|
||||||
OP_CHECKMULTISIG
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def addLockRefundSigs(self, xmr_swap, ci):
|
def addLockRefundSigs(self, xmr_swap, ci):
|
||||||
self.log.debug('Setting lock refund tx sigs')
|
self.log.debug("Setting lock refund tx sigs")
|
||||||
|
|
||||||
witness_stack = []
|
witness_stack = []
|
||||||
if ci.coin_type() not in (Coins.DCR,):
|
if ci.coin_type() not in (Coins.DCR,):
|
||||||
witness_stack += [b'', ]
|
witness_stack += [
|
||||||
|
b"",
|
||||||
|
]
|
||||||
witness_stack += [
|
witness_stack += [
|
||||||
xmr_swap.al_lock_refund_tx_sig,
|
xmr_swap.al_lock_refund_tx_sig,
|
||||||
xmr_swap.af_lock_refund_tx_sig,
|
xmr_swap.af_lock_refund_tx_sig,
|
||||||
@@ -38,64 +39,125 @@ def addLockRefundSigs(self, xmr_swap, ci):
|
|||||||
]
|
]
|
||||||
|
|
||||||
signed_tx = ci.setTxSignature(xmr_swap.a_lock_refund_tx, witness_stack)
|
signed_tx = ci.setTxSignature(xmr_swap.a_lock_refund_tx, witness_stack)
|
||||||
ensure(signed_tx, 'setTxSignature failed')
|
ensure(signed_tx, "setTxSignature failed")
|
||||||
xmr_swap.a_lock_refund_tx = signed_tx
|
xmr_swap.a_lock_refund_tx = signed_tx
|
||||||
|
|
||||||
|
|
||||||
def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
|
def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
|
||||||
self.log.info('Manually recovering %s', bid_id.hex())
|
self.log.info(f"Manually recovering {self.log.id(bid_id)}")
|
||||||
# Manually recover txn if other key is known
|
# Manually recover txn if other key is known
|
||||||
session = scoped_session(self.session_factory)
|
|
||||||
try:
|
try:
|
||||||
bid, xmr_swap = self.getXmrBidFromSession(session, bid_id)
|
use_cursor = self.openDB(cursor)
|
||||||
ensure(bid, 'Bid not found: {}.'.format(bid_id.hex()))
|
bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id)
|
||||||
ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex()))
|
ensure(bid, "Bid not found: {}.".format(bid_id.hex()))
|
||||||
offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False)
|
ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex()))
|
||||||
ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex()))
|
offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id)
|
||||||
ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex()))
|
ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex()))
|
||||||
ci_to = self.ci(offer.coin_to)
|
ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()))
|
||||||
|
|
||||||
for_ed25519 = True if Coins(offer.coin_to) == Coins.XMR else False
|
# The no-script coin is always the follower
|
||||||
|
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to)
|
||||||
|
ci_from = self.ci(Coins(offer.coin_from))
|
||||||
|
ci_to = self.ci(Coins(offer.coin_to))
|
||||||
|
ci_follower = ci_from if reverse_bid else ci_to
|
||||||
|
|
||||||
try:
|
try:
|
||||||
decoded_key_half = ci_to.decodeKey(encoded_key)
|
decoded_key_half = ci_follower.decodeKey(encoded_key)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError('Failed to decode provided key-half: ', str(e))
|
raise ValueError("Failed to decode provided key-half: ", str(e))
|
||||||
|
|
||||||
if bid.was_sent:
|
was_sent: bool = bid.was_received if reverse_bid else bid.was_sent
|
||||||
|
|
||||||
|
localkeyhalf = ci_follower.decodeKey(
|
||||||
|
getChainBSplitKey(self, bid, xmr_swap, offer)
|
||||||
|
)
|
||||||
|
if was_sent:
|
||||||
kbsl = decoded_key_half
|
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 = localkeyhalf
|
||||||
else:
|
else:
|
||||||
kbsl = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519)
|
kbsl = localkeyhalf
|
||||||
kbsf = decoded_key_half
|
kbsf = decoded_key_half
|
||||||
ensure(ci_to.verifyKey(kbsl), 'Invalid kbsl')
|
|
||||||
ensure(ci_to.verifyKey(kbsf), 'Invalid kbsf')
|
|
||||||
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
|
||||||
|
|
||||||
if offer.coin_to == Coins.XMR:
|
ensure(ci_follower.verifyKey(kbsl), "Invalid kbsl")
|
||||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
ensure(ci_follower.verifyKey(kbsf), "Invalid kbsf")
|
||||||
|
if kbsl == kbsf:
|
||||||
|
raise ValueError("Provided key matches local key")
|
||||||
|
vkbs = ci_follower.sumKeys(kbsl, kbsf)
|
||||||
|
|
||||||
|
ensure(ci_follower.verifyPubkey(xmr_swap.pkbs), "Invalid pkbs") # Sanity check
|
||||||
|
|
||||||
|
# Ensure summed key matches the expected pubkey
|
||||||
|
summed_pkbs = ci_follower.getPubkey(vkbs)
|
||||||
|
if summed_pkbs != xmr_swap.pkbs:
|
||||||
|
err_msg: str = "Summed key does not match expected wallet spend pubkey"
|
||||||
|
self.log.error(
|
||||||
|
f"{err_msg}. Got: {summed_pkbs.hex()}, Expect: {xmr_swap.pkbs.hex()}"
|
||||||
|
)
|
||||||
|
raise ValueError(err_msg)
|
||||||
|
|
||||||
|
coin_to: int = ci_follower.interface_type()
|
||||||
|
base_coin_to: int = ci_follower.coin_type()
|
||||||
|
if coin_to in (Coins.XMR, Coins.WOW):
|
||||||
|
address_to = self.getCachedMainWalletAddress(ci_follower, use_cursor)
|
||||||
|
elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
|
||||||
|
address_to = self.getCachedStealthAddressForCoin(base_coin_to, use_cursor)
|
||||||
else:
|
else:
|
||||||
address_to = self.getCachedStealthAddressForCoin(offer.coin_to)
|
address_to = self.getReceiveAddressFromPool(
|
||||||
|
base_coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, use_cursor
|
||||||
|
)
|
||||||
amount = bid.amount_to
|
amount = bid.amount_to
|
||||||
lock_tx_vout = bid.getLockTXBVout()
|
lock_tx_vout = bid.getLockTXBVout()
|
||||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout)
|
txid = ci_follower.spendBLockTx(
|
||||||
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
xmr_swap.b_lock_tx_id,
|
||||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), session)
|
address_to,
|
||||||
session.commit()
|
xmr_swap.vkbv,
|
||||||
|
vkbs,
|
||||||
|
amount,
|
||||||
|
xmr_offer.b_fee_rate,
|
||||||
|
bid.chain_b_height_start,
|
||||||
|
spend_actual_balance=True,
|
||||||
|
lock_tx_vout=lock_tx_vout,
|
||||||
|
)
|
||||||
|
self.log.debug(
|
||||||
|
f"Submitted lock B spend txn {self.log.id(txid)} to {ci_follower.coin_name()} chain for bid {self.log.id(bid_id)}."
|
||||||
|
)
|
||||||
|
self.logBidEvent(
|
||||||
|
bid.bid_id,
|
||||||
|
EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED,
|
||||||
|
txid.hex(),
|
||||||
|
use_cursor,
|
||||||
|
)
|
||||||
|
self.commitDB()
|
||||||
|
|
||||||
return txid
|
return txid
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error(traceback.format_exc())
|
||||||
|
raise (e)
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
if cursor is None:
|
||||||
session.remove()
|
self.closeDB(use_cursor, commit=False)
|
||||||
|
|
||||||
|
|
||||||
def getChainBSplitKey(swap_client, bid, xmr_swap, offer):
|
def getChainBSplitKey(swap_client, bid, xmr_swap, offer):
|
||||||
reverse_bid: bool = offer.bid_reversed
|
reverse_bid: bool = offer.bid_reversed
|
||||||
|
ci_leader = swap_client.ci(offer.coin_to if reverse_bid else offer.coin_from)
|
||||||
ci_follower = swap_client.ci(offer.coin_from if reverse_bid else offer.coin_to)
|
ci_follower = swap_client.ci(offer.coin_from if reverse_bid else offer.coin_to)
|
||||||
|
|
||||||
key_type = KeyTypes.KBSF if bid.was_sent else KeyTypes.KBSL
|
for_ed25519: bool = True if ci_follower.curve_type() == Curves.ed25519 else False
|
||||||
return ci_follower.encodeKey(swap_client.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, key_type, True if ci_follower.coin_type() == Coins.XMR else False))
|
was_sent: bool = bid.was_received if reverse_bid else bid.was_sent
|
||||||
|
|
||||||
|
key_type = KeyTypes.KBSF if was_sent else KeyTypes.KBSL
|
||||||
|
|
||||||
|
return ci_follower.encodeKey(
|
||||||
|
swap_client.getPathKey(
|
||||||
|
ci_leader.interface_type(),
|
||||||
|
ci_follower.interface_type(),
|
||||||
|
bid.created_at,
|
||||||
|
xmr_swap.contract_count,
|
||||||
|
key_type,
|
||||||
|
for_ed25519,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
|
def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
|
||||||
@@ -105,13 +167,21 @@ def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer):
|
|||||||
|
|
||||||
if bid.was_sent:
|
if bid.was_sent:
|
||||||
if xmr_swap.a_lock_refund_spend_tx:
|
if xmr_swap.a_lock_refund_spend_tx:
|
||||||
af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(xmr_swap.a_lock_refund_spend_tx)
|
af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(
|
||||||
kbsl = ci_leader.recoverEncKey(xmr_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xmr_swap.pkasl)
|
xmr_swap.a_lock_refund_spend_tx
|
||||||
|
)
|
||||||
|
kbsl = ci_leader.recoverEncKey(
|
||||||
|
xmr_swap.af_lock_refund_spend_tx_esig,
|
||||||
|
af_lock_refund_spend_tx_sig,
|
||||||
|
xmr_swap.pkasl,
|
||||||
|
)
|
||||||
return ci_follower.encodeKey(kbsl)
|
return ci_follower.encodeKey(kbsl)
|
||||||
else:
|
else:
|
||||||
if xmr_swap.a_lock_spend_tx:
|
if xmr_swap.a_lock_spend_tx:
|
||||||
al_lock_spend_tx_sig = ci_leader.extractLeaderSig(xmr_swap.a_lock_spend_tx)
|
al_lock_spend_tx_sig = ci_leader.extractLeaderSig(xmr_swap.a_lock_spend_tx)
|
||||||
kbsf = ci_leader.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xmr_swap.pkasf)
|
kbsf = ci_leader.recoverEncKey(
|
||||||
|
xmr_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xmr_swap.pkasf
|
||||||
|
)
|
||||||
return ci_follower.encodeKey(kbsf)
|
return ci_follower.encodeKey(kbsf)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -122,21 +192,29 @@ def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
|
|||||||
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33]
|
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33]
|
||||||
elif ci_to.curve_type() == Curves.secp256k1:
|
elif ci_to.curve_type() == Curves.secp256k1:
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
xmr_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, 'proof kbsf owned for swap')
|
xmr_swap.kbsf_dleag = ci_to.signRecoverable(
|
||||||
pk_recovered: bytes = ci_to.verifySigAndRecover(xmr_swap.kbsf_dleag, 'proof kbsf owned for swap')
|
kbsf, "proof kbsf owned for swap"
|
||||||
|
)
|
||||||
|
pk_recovered: bytes = ci_to.verifySigAndRecover(
|
||||||
|
xmr_swap.kbsf_dleag, "proof kbsf owned for swap"
|
||||||
|
)
|
||||||
if pk_recovered == xmr_swap.pkbsf:
|
if pk_recovered == xmr_swap.pkbsf:
|
||||||
break
|
break
|
||||||
# self.log.debug('kbsl recovered pubkey mismatch, retrying.')
|
# self.log.debug('kbsl recovered pubkey mismatch, retrying.')
|
||||||
assert (pk_recovered == xmr_swap.pkbsf)
|
assert pk_recovered == xmr_swap.pkbsf
|
||||||
xmr_swap.pkasf = xmr_swap.pkbsf
|
xmr_swap.pkasf = xmr_swap.pkbsf
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown curve')
|
raise ValueError("Unknown curve")
|
||||||
|
|
||||||
|
|
||||||
class XmrSwapInterface(ProtocolInterface):
|
class XmrSwapInterface(ProtocolInterface):
|
||||||
swap_type = SwapTypes.XMR_SWAP
|
swap_type = SwapTypes.XMR_SWAP
|
||||||
|
|
||||||
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes) -> CScript:
|
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
|
||||||
|
# fallthrough to ci if genScriptLockTxScript is implemented there
|
||||||
|
if hasattr(ci, "genScriptLockTxScript") and callable(ci.genScriptLockTxScript):
|
||||||
|
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
|
||||||
|
|
||||||
Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
|
Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal)
|
||||||
Kaf_enc = Kaf if len(Kaf) == 33 else ci.encodePubkey(Kaf)
|
Kaf_enc = Kaf if len(Kaf) == 33 else ci.encodePubkey(Kaf)
|
||||||
|
|
||||||
@@ -144,7 +222,9 @@ class XmrSwapInterface(ProtocolInterface):
|
|||||||
|
|
||||||
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
||||||
addr_to = self.getMockAddrTo(ci)
|
addr_to = self.getMockAddrTo(ci)
|
||||||
funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False)
|
funded_tx = ci.createRawFundedTransaction(
|
||||||
|
addr_to, amount, sub_fee, lock_unspents=False
|
||||||
|
)
|
||||||
|
|
||||||
return bytes.fromhex(funded_tx)
|
return bytes.fromhex(funded_tx)
|
||||||
|
|
||||||
@@ -160,9 +240,9 @@ class XmrSwapInterface(ProtocolInterface):
|
|||||||
found += 1
|
found += 1
|
||||||
|
|
||||||
if found < 1:
|
if found < 1:
|
||||||
raise ValueError('Mocked output not found')
|
raise ValueError("Mocked output not found")
|
||||||
if found > 1:
|
if found > 1:
|
||||||
raise ValueError('Too many mocked outputs found')
|
raise ValueError("Too many mocked outputs found")
|
||||||
ctx.nLockTime = 0
|
ctx.nLockTime = 0
|
||||||
|
|
||||||
return ctx.serialize()
|
return ctx.serialize()
|
||||||
|
|||||||
114
basicswap/rpc.py
@@ -18,31 +18,42 @@ from xmlrpc.client import (
|
|||||||
from .util import jsonDecimal
|
from .util import jsonDecimal
|
||||||
|
|
||||||
|
|
||||||
class Jsonrpc():
|
class Jsonrpc:
|
||||||
# __getattr__ complicates extending ServerProxy
|
# __getattr__ complicates extending ServerProxy
|
||||||
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
def __init__(
|
||||||
allow_none=False, use_datetime=False, use_builtin_types=False,
|
self,
|
||||||
*, context=None):
|
uri,
|
||||||
|
transport=None,
|
||||||
|
encoding=None,
|
||||||
|
verbose=False,
|
||||||
|
allow_none=False,
|
||||||
|
use_datetime=False,
|
||||||
|
use_builtin_types=False,
|
||||||
|
*,
|
||||||
|
context=None,
|
||||||
|
):
|
||||||
# establish a "logical" server connection
|
# establish a "logical" server connection
|
||||||
|
|
||||||
# get the url
|
# get the url
|
||||||
parsed = urllib.parse.urlparse(uri)
|
parsed = urllib.parse.urlparse(uri)
|
||||||
if parsed.scheme not in ('http', 'https'):
|
if parsed.scheme not in ("http", "https"):
|
||||||
raise OSError('unsupported XML-RPC protocol')
|
raise OSError("unsupported XML-RPC protocol")
|
||||||
self.__host = parsed.netloc
|
self.__host = parsed.netloc
|
||||||
self.__handler = parsed.path
|
self.__handler = parsed.path
|
||||||
if not self.__handler:
|
if not self.__handler:
|
||||||
self.__handler = '/RPC2'
|
self.__handler = "/RPC2"
|
||||||
|
|
||||||
if transport is None:
|
if transport is None:
|
||||||
handler = SafeTransport if parsed.scheme == 'https' else Transport
|
handler = SafeTransport if parsed.scheme == "https" else Transport
|
||||||
extra_kwargs = {}
|
extra_kwargs = {}
|
||||||
transport = handler(use_datetime=use_datetime,
|
transport = handler(
|
||||||
|
use_datetime=use_datetime,
|
||||||
use_builtin_types=use_builtin_types,
|
use_builtin_types=use_builtin_types,
|
||||||
**extra_kwargs)
|
**extra_kwargs,
|
||||||
|
)
|
||||||
self.__transport = transport
|
self.__transport = transport
|
||||||
|
|
||||||
self.__encoding = encoding or 'utf-8'
|
self.__encoding = encoding or "utf-8"
|
||||||
self.__verbose = verbose
|
self.__verbose = verbose
|
||||||
self.__allow_none = allow_none
|
self.__allow_none = allow_none
|
||||||
|
|
||||||
@@ -57,17 +68,16 @@ class Jsonrpc():
|
|||||||
connection = self.__transport.make_connection(self.__host)
|
connection = self.__transport.make_connection(self.__host)
|
||||||
headers = self.__transport._extra_headers[:]
|
headers = self.__transport._extra_headers[:]
|
||||||
|
|
||||||
request_body = {
|
request_body = {"method": method, "params": params, "id": self.__request_id}
|
||||||
'method': method,
|
|
||||||
'params': params,
|
|
||||||
'id': self.__request_id
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.putrequest('POST', self.__handler)
|
connection.putrequest("POST", self.__handler)
|
||||||
headers.append(('Content-Type', 'application/json'))
|
headers.append(("Content-Type", "application/json"))
|
||||||
headers.append(('User-Agent', 'jsonrpc'))
|
headers.append(("User-Agent", "jsonrpc"))
|
||||||
self.__transport.send_headers(connection, headers)
|
self.__transport.send_headers(connection, headers)
|
||||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8'))
|
self.__transport.send_content(
|
||||||
|
connection,
|
||||||
|
json.dumps(request_body, default=jsonDecimal).encode("utf-8"),
|
||||||
|
)
|
||||||
self.__request_id += 1
|
self.__request_id += 1
|
||||||
|
|
||||||
resp = connection.getresponse()
|
resp = connection.getresponse()
|
||||||
@@ -82,55 +92,59 @@ class Jsonrpc():
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def callrpc(rpc_port, auth, method, params=[], wallet=None, host='127.0.0.1'):
|
def callrpc(rpc_port, auth, method, params=[], wallet=None, host="127.0.0.1"):
|
||||||
try:
|
try:
|
||||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
|
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
||||||
if wallet is not None:
|
if wallet is not None:
|
||||||
url += 'wallet/' + urllib.parse.quote(wallet)
|
url += "wallet/" + urllib.parse.quote(wallet)
|
||||||
x = Jsonrpc(url)
|
x = Jsonrpc(url)
|
||||||
|
|
||||||
v = x.json_request(method, params)
|
v = x.json_request(method, params)
|
||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode("utf-8"))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method)
|
raise ValueError("RPC server error " + str(ex) + ", method: " + method)
|
||||||
|
|
||||||
if 'error' in r and r['error'] is not None:
|
if "error" in r and r["error"] is not None:
|
||||||
raise ValueError('RPC error ' + str(r['error']))
|
raise ValueError("RPC error " + str(r["error"]))
|
||||||
|
|
||||||
return r['result']
|
return r["result"]
|
||||||
|
|
||||||
|
|
||||||
def openrpc(rpc_port, auth, wallet=None, host='127.0.0.1'):
|
def openrpc(rpc_port, auth, wallet=None, host="127.0.0.1"):
|
||||||
try:
|
try:
|
||||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
|
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
||||||
if wallet is not None:
|
if wallet is not None:
|
||||||
url += 'wallet/' + urllib.parse.quote(wallet)
|
url += "wallet/" + urllib.parse.quote(wallet)
|
||||||
return Jsonrpc(url)
|
return Jsonrpc(url)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise ValueError('RPC error ' + str(ex))
|
raise ValueError("RPC error " + str(ex))
|
||||||
|
|
||||||
|
|
||||||
def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli', wallet=None):
|
def callrpc_cli(bindir, datadir, chain, cmd, cli_bin="particl-cli", wallet=None):
|
||||||
cli_bin = os.path.join(bindir, cli_bin)
|
cli_bin = os.path.join(bindir, cli_bin)
|
||||||
|
|
||||||
args = [cli_bin, ]
|
args = [
|
||||||
if chain != 'mainnet':
|
cli_bin,
|
||||||
args.append('-' + chain)
|
]
|
||||||
args.append('-datadir=' + datadir)
|
if chain != "mainnet":
|
||||||
|
args.append("-" + chain)
|
||||||
|
args.append("-datadir=" + datadir)
|
||||||
if wallet is not None:
|
if wallet is not None:
|
||||||
args.append('-rpcwallet=' + wallet)
|
args.append("-rpcwallet=" + wallet)
|
||||||
args += shlex.split(cmd)
|
args += shlex.split(cmd)
|
||||||
|
|
||||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
p = subprocess.Popen(
|
||||||
|
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
out = p.communicate()
|
out = p.communicate()
|
||||||
|
|
||||||
if len(out[1]) > 0:
|
if len(out[1]) > 0:
|
||||||
raise ValueError('RPC error ' + str(out[1]))
|
raise ValueError("RPC error " + str(out[1]))
|
||||||
|
|
||||||
r = out[0].decode('utf-8').strip()
|
r = out[0].decode("utf-8").strip()
|
||||||
try:
|
try:
|
||||||
r = json.loads(r)
|
r = json.loads(r)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -138,7 +152,7 @@ def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli', wallet=None)
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def make_rpc_func(port, auth, wallet=None, host='127.0.0.1'):
|
def make_rpc_func(port, auth, wallet=None, host="127.0.0.1"):
|
||||||
port = port
|
port = port
|
||||||
auth = auth
|
auth = auth
|
||||||
wallet = wallet
|
wallet = wallet
|
||||||
@@ -146,11 +160,19 @@ def make_rpc_func(port, auth, wallet=None, host='127.0.0.1'):
|
|||||||
|
|
||||||
def rpc_func(method, params=None, wallet_override=None):
|
def rpc_func(method, params=None, wallet_override=None):
|
||||||
nonlocal port, auth, wallet, host
|
nonlocal port, auth, wallet, host
|
||||||
return callrpc(port, auth, method, params, wallet if wallet_override is None else wallet_override, host)
|
return callrpc(
|
||||||
|
port,
|
||||||
|
auth,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
wallet if wallet_override is None else wallet_override,
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|
||||||
|
|
||||||
def escape_rpcauth(auth_str: str) -> str:
|
def escape_rpcauth(auth_str: str) -> str:
|
||||||
username, password = auth_str.split(':', 1)
|
username, password = auth_str.split(":", 1)
|
||||||
password = urllib.parse.quote(password, safe='')
|
password = urllib.parse.quote(password, safe="")
|
||||||
return f'{username}:{password}'
|
return f"{username}:{password}"
|
||||||
|
|||||||
@@ -33,31 +33,50 @@ class SocksTransport(Transport):
|
|||||||
return self._connection[1]
|
return self._connection[1]
|
||||||
# create a HTTP connection object from a host descriptor
|
# create a HTTP connection object from a host descriptor
|
||||||
chost, self._extra_headers, x509 = self.get_host_info(host)
|
chost, self._extra_headers, x509 = self.get_host_info(host)
|
||||||
self._connection = host, SocksiPyConnection(self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_username, self.proxy_password, chost)
|
self._connection = host, SocksiPyConnection(
|
||||||
|
self.proxy_type,
|
||||||
|
self.proxy_host,
|
||||||
|
self.proxy_port,
|
||||||
|
self.proxy_rdns,
|
||||||
|
self.proxy_username,
|
||||||
|
self.proxy_password,
|
||||||
|
chost,
|
||||||
|
)
|
||||||
return self._connection[1]
|
return self._connection[1]
|
||||||
|
|
||||||
|
|
||||||
class JsonrpcDigest():
|
class JsonrpcDigest:
|
||||||
# __getattr__ complicates extending ServerProxy
|
# __getattr__ complicates extending ServerProxy
|
||||||
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
def __init__(
|
||||||
allow_none=False, use_datetime=False, use_builtin_types=False,
|
self,
|
||||||
*, context=None):
|
uri,
|
||||||
|
transport=None,
|
||||||
|
encoding=None,
|
||||||
|
verbose=False,
|
||||||
|
allow_none=False,
|
||||||
|
use_datetime=False,
|
||||||
|
use_builtin_types=False,
|
||||||
|
*,
|
||||||
|
context=None,
|
||||||
|
):
|
||||||
|
|
||||||
parsed = urllib.parse.urlparse(uri)
|
parsed = urllib.parse.urlparse(uri)
|
||||||
if parsed.scheme not in ('http', 'https'):
|
if parsed.scheme not in ("http", "https"):
|
||||||
raise OSError('unsupported XML-RPC protocol')
|
raise OSError("unsupported XML-RPC protocol")
|
||||||
self.__host = parsed.netloc
|
self.__host = parsed.netloc
|
||||||
self.__handler = parsed.path
|
self.__handler = parsed.path
|
||||||
|
|
||||||
if transport is None:
|
if transport is None:
|
||||||
handler = SafeTransport if parsed.scheme == 'https' else Transport
|
handler = SafeTransport if parsed.scheme == "https" else Transport
|
||||||
extra_kwargs = {}
|
extra_kwargs = {}
|
||||||
transport = handler(use_datetime=use_datetime,
|
transport = handler(
|
||||||
|
use_datetime=use_datetime,
|
||||||
use_builtin_types=use_builtin_types,
|
use_builtin_types=use_builtin_types,
|
||||||
**extra_kwargs)
|
**extra_kwargs,
|
||||||
|
)
|
||||||
self.__transport = transport
|
self.__transport = transport
|
||||||
|
|
||||||
self.__encoding = encoding or 'utf-8'
|
self.__encoding = encoding or "utf-8"
|
||||||
self.__verbose = verbose
|
self.__verbose = verbose
|
||||||
self.__allow_none = allow_none
|
self.__allow_none = allow_none
|
||||||
|
|
||||||
@@ -77,11 +96,18 @@ class JsonrpcDigest():
|
|||||||
connection.timeout = timeout
|
connection.timeout = timeout
|
||||||
headers = self.__transport._extra_headers[:]
|
headers = self.__transport._extra_headers[:]
|
||||||
|
|
||||||
connection.putrequest('POST', self.__handler)
|
connection.putrequest("POST", self.__handler)
|
||||||
headers.append(('Content-Type', 'application/json'))
|
headers.append(("Content-Type", "application/json"))
|
||||||
headers.append(('User-Agent', 'jsonrpc'))
|
headers.append(("User-Agent", "jsonrpc"))
|
||||||
self.__transport.send_headers(connection, headers)
|
self.__transport.send_headers(connection, headers)
|
||||||
self.__transport.send_content(connection, '' if params is None else json.dumps(params, default=jsonDecimal).encode('utf-8'))
|
self.__transport.send_content(
|
||||||
|
connection,
|
||||||
|
(
|
||||||
|
""
|
||||||
|
if params is None
|
||||||
|
else json.dumps(params, default=jsonDecimal).encode("utf-8")
|
||||||
|
),
|
||||||
|
)
|
||||||
self.__request_id += 1
|
self.__request_id += 1
|
||||||
|
|
||||||
resp = connection.getresponse()
|
resp = connection.getresponse()
|
||||||
@@ -93,7 +119,7 @@ class JsonrpcDigest():
|
|||||||
self.__transport.close()
|
self.__transport.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def json_request(self, request_body, username='', password='', timeout=None):
|
def json_request(self, request_body, username="", password="", timeout=None):
|
||||||
try:
|
try:
|
||||||
connection = self.__transport.make_connection(self.__host)
|
connection = self.__transport.make_connection(self.__host)
|
||||||
if timeout:
|
if timeout:
|
||||||
@@ -101,65 +127,82 @@ class JsonrpcDigest():
|
|||||||
|
|
||||||
headers = self.__transport._extra_headers[:]
|
headers = self.__transport._extra_headers[:]
|
||||||
|
|
||||||
connection.putrequest('POST', self.__handler)
|
connection.putrequest("POST", self.__handler)
|
||||||
headers.append(('Content-Type', 'application/json'))
|
headers.append(("Content-Type", "application/json"))
|
||||||
headers.append(('Connection', 'keep-alive'))
|
headers.append(("Connection", "keep-alive"))
|
||||||
self.__transport.send_headers(connection, headers)
|
self.__transport.send_headers(connection, headers)
|
||||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
self.__transport.send_content(
|
||||||
|
connection,
|
||||||
|
(
|
||||||
|
json.dumps(request_body, default=jsonDecimal).encode("utf-8")
|
||||||
|
if request_body
|
||||||
|
else ""
|
||||||
|
),
|
||||||
|
)
|
||||||
resp = connection.getresponse()
|
resp = connection.getresponse()
|
||||||
|
|
||||||
if resp.status == 401:
|
if resp.status == 401:
|
||||||
resp_headers = resp.getheaders()
|
resp_headers = resp.getheaders()
|
||||||
v = resp.read()
|
_ = resp.read()
|
||||||
|
|
||||||
algorithm = ''
|
realm = ""
|
||||||
realm = ''
|
nonce = ""
|
||||||
nonce = ''
|
|
||||||
for h in resp_headers:
|
for h in resp_headers:
|
||||||
if h[0] != 'WWW-authenticate':
|
if h[0] != "WWW-authenticate":
|
||||||
continue
|
continue
|
||||||
fields = h[1].split(',')
|
fields = h[1].split(",")
|
||||||
for f in fields:
|
for f in fields:
|
||||||
key, value = f.split('=', 1)
|
key, value = f.split("=", 1)
|
||||||
if key == 'algorithm' and value != 'MD5':
|
if key == "algorithm" and value != "MD5":
|
||||||
break
|
break
|
||||||
if key == 'realm':
|
if key == "realm":
|
||||||
realm = value.strip('"')
|
realm = value.strip('"')
|
||||||
if key == 'nonce':
|
if key == "nonce":
|
||||||
nonce = value.strip('"')
|
nonce = value.strip('"')
|
||||||
if realm != '' and nonce != '':
|
if realm != "" and nonce != "":
|
||||||
break
|
break
|
||||||
|
|
||||||
if realm == '' or nonce == '':
|
if realm == "" or nonce == "":
|
||||||
raise ValueError('Authenticate header not found.')
|
raise ValueError("Authenticate header not found.")
|
||||||
|
|
||||||
path = self.__handler
|
path = self.__handler
|
||||||
HA1 = hashlib.md5(f'{username}:{realm}:{password}'.encode('utf-8')).hexdigest()
|
HA1 = hashlib.md5(
|
||||||
|
f"{username}:{realm}:{password}".encode("utf-8")
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
http_method = 'POST'
|
http_method = "POST"
|
||||||
HA2 = hashlib.md5(f'{http_method}:{path}'.encode('utf-8')).hexdigest()
|
HA2 = hashlib.md5(f"{http_method}:{path}".encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
ncvalue = '{:08x}'.format(1)
|
ncvalue = "{:08x}".format(1)
|
||||||
s = ncvalue.encode('utf-8')
|
s = ncvalue.encode("utf-8")
|
||||||
s += nonce.encode('utf-8')
|
s += nonce.encode("utf-8")
|
||||||
s += time.ctime().encode('utf-8')
|
s += time.ctime().encode("utf-8")
|
||||||
s += os.urandom(8)
|
s += os.urandom(8)
|
||||||
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
cnonce = hashlib.sha1(s).hexdigest()[:16]
|
||||||
|
|
||||||
# MD5-SESS
|
# MD5-SESS
|
||||||
HA1 = hashlib.md5(f'{HA1}:{nonce}:{cnonce}'.encode('utf-8')).hexdigest()
|
HA1 = hashlib.md5(f"{HA1}:{nonce}:{cnonce}".encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
respdig = hashlib.md5(f'{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}'.encode('utf-8')).hexdigest()
|
respdig = hashlib.md5(
|
||||||
|
f"{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}".encode("utf-8")
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
|
header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
|
||||||
headers = self.__transport._extra_headers[:]
|
headers = self.__transport._extra_headers[:]
|
||||||
headers.append(('Authorization', header_value))
|
headers.append(("Authorization", header_value))
|
||||||
|
|
||||||
connection.putrequest('POST', self.__handler)
|
connection.putrequest("POST", self.__handler)
|
||||||
headers.append(('Content-Type', 'application/json'))
|
headers.append(("Content-Type", "application/json"))
|
||||||
headers.append(('Connection', 'keep-alive'))
|
headers.append(("Connection", "keep-alive"))
|
||||||
self.__transport.send_headers(connection, headers)
|
self.__transport.send_headers(connection, headers)
|
||||||
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
self.__transport.send_content(
|
||||||
|
connection,
|
||||||
|
(
|
||||||
|
json.dumps(request_body, default=jsonDecimal).encode("utf-8")
|
||||||
|
if request_body
|
||||||
|
else ""
|
||||||
|
),
|
||||||
|
)
|
||||||
resp = connection.getresponse()
|
resp = connection.getresponse()
|
||||||
|
|
||||||
self.__request_id += 1
|
self.__request_id += 1
|
||||||
@@ -172,57 +215,88 @@ class JsonrpcDigest():
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None, tag=''):
|
def callrpc_xmr(
|
||||||
|
rpc_port,
|
||||||
|
method,
|
||||||
|
params=[],
|
||||||
|
rpc_host="127.0.0.1",
|
||||||
|
path="json_rpc",
|
||||||
|
auth=None,
|
||||||
|
timeout=120,
|
||||||
|
transport=None,
|
||||||
|
tag="",
|
||||||
|
):
|
||||||
# auth is a tuple: (username, password)
|
# auth is a tuple: (username, password)
|
||||||
try:
|
try:
|
||||||
if rpc_host.count('://') > 0:
|
if rpc_host.count("://") > 0:
|
||||||
url = '{}:{}/{}'.format(rpc_host, rpc_port, path)
|
url = "{}:{}/{}".format(rpc_host, rpc_port, path)
|
||||||
else:
|
else:
|
||||||
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
|
url = "http://{}:{}/{}".format(rpc_host, rpc_port, path)
|
||||||
|
|
||||||
x = JsonrpcDigest(url, transport=transport)
|
x = JsonrpcDigest(url, transport=transport)
|
||||||
request_body = {
|
request_body = {
|
||||||
'method': method,
|
"method": method,
|
||||||
'params': params,
|
"params": params,
|
||||||
'jsonrpc': '2.0',
|
"jsonrpc": "2.0",
|
||||||
'id': x.request_id()
|
"id": x.request_id(),
|
||||||
}
|
}
|
||||||
if auth:
|
if auth:
|
||||||
v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout)
|
v = x.json_request(
|
||||||
|
request_body, username=auth[0], password=auth[1], timeout=timeout
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
v = x.json_request(request_body, timeout=timeout)
|
v = x.json_request(request_body, timeout=timeout)
|
||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode("utf-8"))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
raise ValueError("{}RPC Server Error: {}".format(tag, str(ex)))
|
||||||
|
|
||||||
if 'error' in r and r['error'] is not None:
|
if "error" in r and r["error"] is not None:
|
||||||
raise ValueError(tag + 'RPC error ' + str(r['error']))
|
raise ValueError(tag + "RPC error " + str(r["error"]))
|
||||||
|
|
||||||
return r['result']
|
return r["result"]
|
||||||
|
|
||||||
|
|
||||||
def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None, tag=''):
|
def callrpc_xmr2(
|
||||||
|
rpc_port: int,
|
||||||
|
method: str,
|
||||||
|
params=None,
|
||||||
|
auth=None,
|
||||||
|
rpc_host="127.0.0.1",
|
||||||
|
timeout=120,
|
||||||
|
transport=None,
|
||||||
|
tag="",
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
if rpc_host.count('://') > 0:
|
if rpc_host.count("://") > 0:
|
||||||
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
|
url = "{}:{}/{}".format(rpc_host, rpc_port, method)
|
||||||
else:
|
else:
|
||||||
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method)
|
url = "http://{}:{}/{}".format(rpc_host, rpc_port, method)
|
||||||
|
|
||||||
x = JsonrpcDigest(url, transport=transport)
|
x = JsonrpcDigest(url, transport=transport)
|
||||||
if auth:
|
if auth:
|
||||||
v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout)
|
v = x.json_request(
|
||||||
|
params, username=auth[0], password=auth[1], timeout=timeout
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
v = x.json_request(params, timeout=timeout)
|
v = x.json_request(params, timeout=timeout)
|
||||||
x.close()
|
x.close()
|
||||||
r = json.loads(v.decode('utf-8'))
|
r = json.loads(v.decode("utf-8"))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
raise ValueError("{}RPC Server Error: {}".format(tag, str(ex)))
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
def make_xmr_rpc2_func(
|
||||||
|
port,
|
||||||
|
auth,
|
||||||
|
host="127.0.0.1",
|
||||||
|
proxy_host=None,
|
||||||
|
proxy_port=None,
|
||||||
|
default_timeout=120,
|
||||||
|
tag="",
|
||||||
|
):
|
||||||
port = port
|
port = port
|
||||||
auth = auth
|
auth = auth
|
||||||
host = host
|
host = host
|
||||||
@@ -236,11 +310,29 @@ def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port
|
|||||||
|
|
||||||
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
||||||
nonlocal port, auth, host, transport, tag
|
nonlocal port, auth, host, transport, tag
|
||||||
return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport, tag=tag)
|
return callrpc_xmr2(
|
||||||
|
port,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
auth=auth,
|
||||||
|
rpc_host=host,
|
||||||
|
timeout=timeout,
|
||||||
|
transport=transport,
|
||||||
|
tag=tag,
|
||||||
|
)
|
||||||
|
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|
||||||
|
|
||||||
def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
def make_xmr_rpc_func(
|
||||||
|
port,
|
||||||
|
auth,
|
||||||
|
host="127.0.0.1",
|
||||||
|
proxy_host=None,
|
||||||
|
proxy_port=None,
|
||||||
|
default_timeout=120,
|
||||||
|
tag="",
|
||||||
|
):
|
||||||
port = port
|
port = port
|
||||||
auth = auth
|
auth = auth
|
||||||
host = host
|
host = host
|
||||||
@@ -254,5 +346,15 @@ def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=
|
|||||||
|
|
||||||
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
||||||
nonlocal port, auth, host, transport, tag
|
nonlocal port, auth, host, transport, tag
|
||||||
return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport, tag=tag)
|
return callrpc_xmr(
|
||||||
|
port,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
rpc_host=host,
|
||||||
|
auth=auth,
|
||||||
|
timeout=timeout,
|
||||||
|
transport=transport,
|
||||||
|
tag=tag,
|
||||||
|
)
|
||||||
|
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ from enum import IntEnum
|
|||||||
|
|
||||||
|
|
||||||
class OpCodes(IntEnum):
|
class OpCodes(IntEnum):
|
||||||
OP_0 = 0x00,
|
OP_0 = (0x00,)
|
||||||
OP_PUSHDATA1 = 0x4c,
|
OP_PUSHDATA1 = (0x4C,)
|
||||||
OP_1 = 0x51,
|
OP_1 = (0x51,)
|
||||||
OP_16 = 0x60,
|
OP_16 = (0x60,)
|
||||||
OP_IF = 0x63,
|
OP_IF = (0x63,)
|
||||||
OP_ELSE = 0x67,
|
OP_ELSE = (0x67,)
|
||||||
OP_ENDIF = 0x68,
|
OP_ENDIF = (0x68,)
|
||||||
OP_RETURN = 0x6a,
|
OP_RETURN = (0x6A,)
|
||||||
OP_DROP = 0x75,
|
OP_DROP = (0x75,)
|
||||||
OP_DUP = 0x76,
|
OP_DUP = (0x76,)
|
||||||
OP_SIZE = 0x82,
|
OP_SIZE = (0x82,)
|
||||||
OP_EQUAL = 0x87,
|
OP_EQUAL = (0x87,)
|
||||||
OP_EQUALVERIFY = 0x88,
|
OP_EQUALVERIFY = (0x88,)
|
||||||
OP_SHA256 = 0xa8,
|
OP_SHA256 = (0xA8,)
|
||||||
OP_HASH160 = 0xa9,
|
OP_HASH160 = (0xA9,)
|
||||||
OP_CHECKSIG = 0xac,
|
OP_CHECKSIG = (0xAC,)
|
||||||
OP_CHECKLOCKTIMEVERIFY = 0xb1,
|
OP_CHECKLOCKTIMEVERIFY = (0xB1,)
|
||||||
OP_CHECKSEQUENCEVERIFY = 0xb2,
|
OP_CHECKSEQUENCEVERIFY = (0xB2,)
|
||||||
|
|
||||||
OP_SHA256_DECRED = 0xc0,
|
OP_SHA256_DECRED = (0xC0,)
|
||||||
|
|||||||
@@ -1,42 +1,39 @@
|
|||||||
|
/* General Styles */
|
||||||
.padded_row td
|
.bold {
|
||||||
{
|
|
||||||
padding-top:1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold
|
|
||||||
{
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monospace
|
.monospace {
|
||||||
{
|
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floatright
|
.floatright {
|
||||||
{
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1.25rem;
|
top: 1.25rem;
|
||||||
right: 1.25rem;
|
right: 1.25rem;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error_msg
|
/* Table Styles */
|
||||||
{
|
.padded_row td {
|
||||||
|
padding-top: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal Styles */
|
||||||
|
.modal-highest {
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation */
|
||||||
#hide {
|
#hide {
|
||||||
-moz-animation: cssAnimation 0s ease-in 15s forwards;
|
-moz-animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
/* Firefox */
|
|
||||||
-webkit-animation: cssAnimation 0s ease-in 15s forwards;
|
-webkit-animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
/* Safari and Chrome */
|
|
||||||
-o-animation: cssAnimation 0s ease-in 15s forwards;
|
-o-animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
/* Opera */
|
|
||||||
animation: cssAnimation 0s ease-in 15s forwards;
|
animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
-webkit-animation-fill-mode: forwards;
|
-webkit-animation-fill-mode: forwards;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes cssAnimation {
|
@keyframes cssAnimation {
|
||||||
to {
|
to {
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -44,6 +41,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes cssAnimation {
|
@-webkit-keyframes cssAnimation {
|
||||||
to {
|
to {
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -52,6 +50,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Select Styles */
|
||||||
.custom-select .select {
|
.custom-select .select {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background-image: url('/static/images/other/coin.png');
|
background-image: url('/static/images/other/coin.png');
|
||||||
@@ -95,8 +94,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Blur and Overlay Styles */
|
||||||
.blurred {
|
.blurred {
|
||||||
filter: blur(4px);
|
filter: blur(3px);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
user-select: auto;
|
user-select: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disable opacity on disabled form elements in Chrome */
|
/* Form Element Styles */
|
||||||
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
||||||
select:disabled,
|
select:disabled,
|
||||||
input:disabled,
|
input:disabled,
|
||||||
@@ -120,6 +120,7 @@
|
|||||||
border: 1px solid red !important;
|
border: 1px solid red !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Active Container Styles */
|
||||||
.active-container {
|
.active-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Center Spin Animation */
|
||||||
.center-spin {
|
.center-spin {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -144,23 +146,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% {
|
||||||
100% { transform: rotate(360deg); }
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-container:hover #coin_to_button, .hover-container:hover #coin_to {
|
/* Hover Container Styles */
|
||||||
border-color: #3b82f6;
|
.hover-container:hover #coin_to_button,
|
||||||
}
|
.hover-container:hover #coin_to,
|
||||||
.hover-container:hover #coin_from_button, .hover-container:hover #coin_from {
|
.hover-container:hover #coin_from_button,
|
||||||
|
.hover-container:hover #coin_from {
|
||||||
border-color: #3b82f6;
|
border-color: #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#coin_to_button, #coin_from_button {
|
#coin_to_button,
|
||||||
|
#coin_from_button {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: 20px 20px;
|
background-size: 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Input-like Container Styles */
|
||||||
.input-like-container {
|
.input-like-container {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
@@ -172,41 +181,45 @@
|
|||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
height: 90px;
|
overflow-wrap: break-word;
|
||||||
color: #374151;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
outline: none;
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
height: auto;
|
||||||
|
min-height: 90px;
|
||||||
|
max-height: 150px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-like-container.dark {
|
||||||
|
background-color: #374151;
|
||||||
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-like-container.copying {
|
.input-like-container.copying {
|
||||||
width: inherit;
|
width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* QR Code Styles */
|
||||||
.qrcode {
|
.qrcode {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.qrcode-border {
|
.qrcode-border {
|
||||||
border: 2px solid;
|
border: 2px solid;
|
||||||
border-color: rgba(59, 130, 246, var(--tw-border-opacity));
|
background-color: #ffffff;
|
||||||
border-radius: 20px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qrcode img {
|
.qrcode img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 15px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#showQR {
|
#showQR {
|
||||||
@@ -217,59 +230,138 @@
|
|||||||
height: 25px;
|
height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qrcode-container {
|
||||||
select.select-disabled {
|
margin-top: 25px;
|
||||||
opacity: 0.40 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disabled Element Styles */
|
||||||
.disabled-input-enabled {
|
select.select-disabled,
|
||||||
opacity: 0.40 !important;
|
.disabled-input-enabled,
|
||||||
}
|
|
||||||
select.disabled-select-enabled {
|
select.disabled-select-enabled {
|
||||||
opacity: 0.40 !important;
|
opacity: 0.40 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Shutdown Modal Styles */
|
||||||
|
#shutdownModal {
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
.custom-select .select {
|
#shutdownModal > div:first-child {
|
||||||
appearance: none;
|
z-index: 40;
|
||||||
background-position: 10px center;
|
}
|
||||||
background-repeat: no-repeat;
|
|
||||||
|
#shutdownModal > div:last-child {
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shutdownModal > div {
|
||||||
|
transition: opacity 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shutdownModal.hidden > div {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shutdownModal:not(.hidden) > div {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutdown-button {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutdown-button.shutdown-disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutdown-button.shutdown-disabled:hover {
|
||||||
|
background-color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shutdown-button.shutdown-disabled svg {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading Line Animation */
|
||||||
|
.loading-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: #ccc;
|
||||||
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select select::-webkit-scrollbar {
|
.loading-line::before {
|
||||||
width: 0;
|
content: '';
|
||||||
}
|
|
||||||
|
|
||||||
.custom-select .select option {
|
|
||||||
padding-left: 0;
|
|
||||||
text-indent: 0;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-select .select option.no-space {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-select .select option[data-image] {
|
|
||||||
background-image: url('');
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-select .select-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 10px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-select .select-image {
|
|
||||||
display: none;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-select .select:focus+.select-dropdown .select-image {
|
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to right, transparent, #007bff, transparent);
|
||||||
|
animation: loading 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.usd-value:not(.loading) .loading-line,
|
||||||
|
.profit-loss:not(.loading) .loading-line {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolution Button Styles */
|
||||||
|
.resolution-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #4B5563;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resolution-button:hover {
|
||||||
|
color: #1F2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resolution-button:focus {
|
||||||
|
outline: 2px solid #3B82F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resolution-button.active {
|
||||||
|
color: #3B82F6;
|
||||||
|
outline: 2px solid #3B82F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .resolution-button {
|
||||||
|
color: #9CA3AF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .resolution-button:hover {
|
||||||
|
color: #F3F4F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .resolution-button.active {
|
||||||
|
color: #60A5FA;
|
||||||
|
outline-color: #60A5FA;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle Button Styles */
|
||||||
|
#toggle-volume.active {
|
||||||
|
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toggle-auto-refresh[data-enabled="true"] {
|
||||||
|
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
||||||
|
}
|
||||||
|
|||||||
BIN
basicswap/static/images/coins/Bitcoin%20Cash.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
basicswap/static/images/coins/Bitcoin-Cash-20.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
basicswap/static/images/coins/Bitcoin-Cash.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |