Merge pull request #21 from MAGICGrants/litecoin

WIP: Support Litecoin
This commit is contained in:
Artur
2025-03-11 17:49:12 -03:00
committed by GitHub
17 changed files with 417 additions and 137 deletions

View File

@@ -1,12 +1,22 @@
ELECTRUM_SERVER_ADDRESS=""
MONERO_DAEMON_ADDRESS=""
ELECTRUM_RPC_PASSWORD=""
MONERO_RPC_PASSWORD=""
BITCOIN_WALLET_SEED=""
LITECOIN_WALLET_SEED=""
LITECOIN_MWEB_WALLET_SEED=""
MONERO_DAEMON_ADDRESS=""
MONERO_RPC_URL=""
MONERO_RPC_PASSWORD=""
MONERO_WALLET_SEED=""
MONERO_WALLET_PASSWORD=""
MONERO_WALLET_HEIGHT=""
KRAKEN_API_KEY=""
KRAKEN_API_SECRET=""
MAX_BITCOIN_FEE_PERCENT="10"
MAX_SLIPPAGE_PERCENT="1"
MAX_NETWORK_FEE_PERCENT="5"
MAX_SLIPPAGE_PERCENT="0.5"
BITCOIN_FEE_SOURCE="https://mempool.space/api/v1/fees/recommended"
BITCOIN_FEE_RATE="halfHourFee"
LITECOIN_FEE_SOURCE="https://litecoinspace.org/api/v1/fees/recommended"
LITECOIN_FEE_RATE="halfHourFee"

View File

@@ -25,16 +25,29 @@ jobs:
git pull
echo "Starting..."
ELECTRUM_RPC_USERNAME="user" \
ELECTRUM_RPC_PASSWORD="${{ secrets.ELECTRUM_RPC_PASSWORD }}" \
BITCOIN_WALLET_SEED="${{ secrets.BITCOIN_WALLET_SEED }}" \
MONERO_DAEMON_ADDRESS="${{ secrets.MONERO_DAEMON_ADDRESS }}" \
MONERO_RPC_USERNAME="user" \
MONERO_RPC_PASSWORD="${{ secrets.MONERO_RPC_PASSWORD }}" \
MONERO_WALLET_SEED="${{ secrets.MONERO_WALLET_SEED }}" \
MONERO_WALLET_PASSWORD="${{ secrets.MONERO_WALLET_PASSWORD }}" \
MONERO_WALLET_HEIGHT="${{ secrets.MONERO_WALLET_HEIGHT }}" \
KRAKEN_API_KEY="${{ secrets.KRAKEN_API_KEY }}" \
KRAKEN_API_SECRET="${{ secrets.KRAKEN_API_SECRET }}" \
ELECTRUM_RPC_PASSWORD=${{ secrets.ELECTRUM_RPC_PASSWORD }} \
BITCOIN_ELECTRUM_SERVER_ADDRESS=${{ secrets.BITCOIN_ELECTRUM_SERVER_ADDRESS }} \
LITECOIN_ELECTRUM_SERVER_ADDRESS=${{ secrets.LITECOIN_ELECTRUM_SERVER_ADDRESS }} \
BITCOIN_WALLET_SEED=${{ secrets.BITCOIN_WALLET_SEED }} \
LITECOIN_WALLET_SEED=${{ secrets.LITECOIN_WALLET_SEED }} \
LITECOIN_MWEB_WALLET_SEED=${{ secrets.LITECOIN_MWEB_WALLET_SEED }} \
MONERO_DAEMON_ADDRESS=${{ secrets.MONERO_DAEMON_ADDRESS }} \
MONERO_RPC_URL=${{ secrets.MONERO_RPC_URL }} \
MONERO_RPC_PASSWORD=${{ secrets.MONERO_RPC_PASSWORD }} \
MONERO_WALLET_SEED=${{ secrets.MONERO_WALLET_SEED }} \
MONERO_WALLET_PASSWORD=${{ secrets.MONERO_WALLET_PASSWORD }} \
MONERO_WALLET_HEIGHT=${{ secrets.MONERO_WALLET_HEIGHT }} \
KRAKEN_API_KEY=${{ secrets.KRAKEN_API_KEY }} \
KRAKEN_API_SECRET=${{ secrets.KRAKEN_API_SECRET }} \
MAX_NETWORK_FEE_PERCENT=${{ secrets.MAX_NETWORK_FEE_PERCENT }} \
MAX_SLIPPAGE_PERCENT=${{ secrets.MAX_SLIPPAGE_PERCENT }} \
BITCOIN_FEE_SOURCE=${{ secrets.BITCOIN_FEE_SOURCE }} \
BITCOIN_FEE_RATE=${{ secrets.BITCOIN_FEE_RATE }} \
LITECOIN_FEE_SOURCE=${{ secrets.LITECOIN_FEE_SOURCE }} \
LITECOIN_FEE_RATE=${{ secrets.LITECOIN_FEE_RATE }} \
docker compose up -d --build
EOF

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Multidisciplinary Academic Grants in Cryptocurrencies
Copyright (c) 2024-2025 MAGIC Grants
Copyright (c) 2022 Cake Labs LLC: https://github.com/cake-tech/autoforward-autoconvert
Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@@ -15,18 +15,24 @@ Create a `.env` file as a copy of `.env.example` and set the values for the empt
| Variable name | Required | Default | Description |
| - | - | - | - |
| `BITCOIN_WALLET_SEED` | Yes | - | Your BIP39 Bitcoin mnemonic seed. |
| `BITCOIN_WALLET_SEED` | Yes | - | Your BIP39 Bitcoin mnemonic seed. Used for all Bitcoin-like assets. |
| `LITECOIN_WALLET_SEED` | Yes | - | Your BIP39 Litecoin mnemonic seed. |
| `MONERO_WALLET_SEED` | Yes | - | Your 25 word Monero mnemonic seed. |
| `MONERO_WALLET_HEIGHT` | Yes | - | The restore height of your Monero wallet. |
| `ELECTRUM_RPC_PASSWORD` | Yes | - | A new strong password for your Electrum RPC. |
| `ELECTRUM_RPC_PASSWORD` | Yes | - | A new strong password for your Electrum RPCs. |
| `MONERO_RPC_PASSWORD` | Yes | - | A new strong password for your Monero RPC. |
| `MONERO_WALLET_PASSWORD` | Yes | - | A new strong password for your Monero Wallet. |
| `KRAKEN_API_KEY` | Yes | - | Your API key from Kraken. |
| `KRAKEN_API_SECRET` | Yes | - | Your API secret from Kraken. |
| `MONERO_DAEMON_ADDRESS` | Yes | - | The address of a Monero daemon you own or trust. |
| `ELECTRUM_SERVER_ADDRESS` | **No** | - | The address of an Electrum server you own or trust. E.g.: `localhost:50001:t` (no SSL) or `my.electrum.server:50001:s` (SSL). By leaving this blank you're letting Electrum select a random server for you, which may be a privacy concern. |
| `MAX_BITCOIN_FEE_PERCENT` | **No** | `10` | The maximum accepted bitcoin miner's fee percent when auto-forwarding. |
| `MAX_SLIPPAGE_PERCENT` | **No** | `1` | The maximum accepted slippage percent when auto-converting. |
| `BITCOIN_ELECTRUM_SERVER_ADDRESS` | **No** | - | The address of a Bitcoin Electrum server you own or trust. E.g.: `localhost:50001:t` (no SSL) or `my.electrum.server:50001:s` (SSL). By leaving this blank you're letting Electrum select a random server for you, which may be a privacy concern. |
| `LITECOIN_ELECTRUM_SERVER_ADDRESS` | **No** | - | The address of a Litecoin Electrum server you own or trust. E.g.: `localhost:50001:t` (no SSL) or `my.electrum.server:50001:s` (SSL). By leaving this blank you're letting Electrum select a random server for you, which may be a privacy concern. |
| `MAX_NETWORK_FEE_PERCENT` | **No** | `5` | The maximum accepted miner fee percent when auto-forwarding. Not applied to XMR. |
| `MAX_SLIPPAGE_PERCENT` | **No** | `0.5` | The maximum accepted slippage percent when auto-converting. |
| `BITCOIN_FEE_SOURCE` | **No** | `https://mempool.space/api/v1/fees/recommended` | The fee API source to use for Bitcoin transactions. |
| `BITCOIN_FEE_RATE` | **No** | `halfHourFee` | The fee rate to use in the Bitcoin fee source API response. |
| `LITECOIN_FEE_SOURCE` | **No** | `https://litecoinspace.org/api/v1/fees/recommended` | The fee API source to use for Litecoin transactions. |
| `LITECOIN_FEE_RATE` | **No** | `halfHourFee` | The fee rate to use in the Litecoin fee source API response. |
## Running

View File

@@ -1,19 +1,59 @@
services:
electrum-client:
btc-electrum:
build:
context: ./electrum-client
context: ./docker/btc-electrum
args:
VERSION: "4.5.5"
CHECKSUM_SHA512: "3bdfce2187466fff20fd67736bdf257bf95d3517de47043be411ccda558a442b8fd81d6a8da094a39a1db39a7339dcd4282e73a7f00cf6bbd70473d7ce456b0b"
container_name: electrum-client
container_name: btc-electrum
restart: unless-stopped
volumes:
- bitcoin-data:/home/electrum/.electrum
environment:
- ELECTRUM_USER=${ELECTRUM_RPC_USERNAME}
- ELECTRUM_PASSWORD=${ELECTRUM_RPC_PASSWORD}
- ELECTRUM_SERVER_ADDRESS=${ELECTRUM_SERVER_ADDRESS}
- ELECTRUM_RPC_USER=user
- ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD}
- ELECTRUM_SERVER_ADDRESS=${BITCOIN_ELECTRUM_SERVER_ADDRESS}
ltc-electrum:
build:
context: ./docker/ltc-electrum
args:
VERSION: "release-9"
CHECKSUM_SHA512: "62248d5eba9b7d67facb767ff35706ef3e3dcd69c6b6fb8fb67b09bc07e52193ecd59f122388d401e854385b2e2b31fd802a9f5d56464472d893f5bc1bd394af"
container_name: ltc-electrum
restart: unless-stopped
cap_add:
- SYS_ADMIN
devices:
- /dev/fuse:/dev/fuse
volumes:
- litecoin-data:/home/electrum-ltc/.electrum
environment:
- ELECTRUM_RPC_USER=user
- ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD}
- ELECTRUM_SERVER_ADDRESS=${LITECOIN_ELECTRUM_SERVER_ADDRESS}
# ltc-mweb-electrum:
# build:
# context: ./docker/ltc-electrum
# args:
# VERSION: "release-9"
# CHECKSUM_SHA512: "62248d5eba9b7d67facb767ff35706ef3e3dcd69c6b6fb8fb67b09bc07e52193ecd59f122388d401e854385b2e2b31fd802a9f5d56464472d893f5bc1bd394af"
# container_name: ltc-mweb-electrum
# restart: unless-stopped
# cap_add:
# - SYS_ADMIN
# devices:
# - "/dev/fuse"
# volumes:
# - litecoin-mweb-data:/home/electrum-ltc/.electrum
# environment:
# - ELECTRUM_RPC_USER=user
# - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD}
# - ELECTRUM_SERVER_ADDRESS=${LITECOIN_ELECTRUM_SERVER_ADDRESS}
monero-wallet-rpc:
image: ghcr.io/sethforprivacy/simple-monero-wallet-rpc:latest
image: sethsimmons/simple-monero-wallet-rpc:latest
restart: unless-stopped
container_name: monero-wallet-rpc
volumes:
@@ -21,7 +61,7 @@ services:
command:
- "--trusted-daemon"
- "--rpc-bind-port=18082"
- "--rpc-login=${MONERO_RPC_USERNAME}:${MONERO_RPC_PASSWORD}"
- "--rpc-login=user:${MONERO_RPC_PASSWORD}"
- "--daemon-address=${MONERO_DAEMON_ADDRESS}"
- "--wallet-dir=/home/monero/wallet"
- "--log-level=4"
@@ -32,19 +72,24 @@ services:
container_name: seed-importer
environment:
- PYTHONUNBUFFERED=1
- ELECTRUM_RPC_URL=http://electrum-client:7000
- ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME}
- BITCOIN_ELECTRUM_RPC_URL=http://btc-electrum:7000
- LITECOIN_ELECTRUM_RPC_URL=http://ltc-electrum:7000
- LITECOIN_MWEB_ELECTRUM_RPC_URL=http://ltc-mweb-electrum:7000
- ELECTRUM_RPC_USERNAME=user
- ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD}
- BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED}
- LITECOIN_WALLET_SEED=${LITECOIN_WALLET_SEED}
- LITECOIN_MWEB_WALLET_SEED=${LITECOIN_MWEB_WALLET_SEED}
- MONERO_RPC_URL=http://monero-wallet-rpc:18082/json_rpc
- MONERO_RPC_USERNAME=${MONERO_RPC_USERNAME}
- MONERO_RPC_USERNAME=user
- MONERO_RPC_PASSWORD=${MONERO_RPC_PASSWORD}
- MONERO_WALLET_SEED=${MONERO_WALLET_SEED}
- MONERO_WALLET_PASSWORD=${MONERO_WALLET_PASSWORD}
- MONERO_WALLET_HEIGHT=${MONERO_WALLET_HEIGHT}
command: python ./src/seed-importer.py
depends_on:
- electrum-client
- btc-electrum
- ltc-electrum
- monero-wallet-rpc
autoforward:
@@ -54,19 +99,19 @@ services:
restart: unless-stopped
environment:
- PYTHONUNBUFFERED=1
- ELECTRUM_RPC_URL=http://electrum-client:7000
- ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME}
- BITCOIN_ELECTRUM_RPC_URL=http://btc-electrum:7000
- LITECOIN_ELECTRUM_RPC_URL=http://ltc-electrum:7000
- LITECOIN_MWEB_ELECTRUM_RPC_URL=http://ltc-mweb-electrum:7000
- ELECTRUM_RPC_USERNAME=user
- ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD}
- BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED}
- MONERO_RPC_URL=http://monero-wallet-rpc:18082/json_rpc
- MONERO_RPC_USERNAME=${MONERO_RPC_USERNAME}
- MONERO_RPC_USERNAME=user
- MONERO_RPC_PASSWORD=${MONERO_RPC_PASSWORD}
- MONERO_WALLET_SEED=${MONERO_WALLET_SEED}
- MONERO_WALLET_PASSWORD=${MONERO_WALLET_PASSWORD}
- MONERO_WALLET_HEIGHT=${MONERO_WALLET_HEIGHT}
- KRAKEN_API_KEY=${KRAKEN_API_KEY}
- KRAKEN_API_SECRET=${KRAKEN_API_SECRET}
command: python ./src/autoforward.py
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
- seed-importer
@@ -84,4 +129,7 @@ services:
- seed-importer
volumes:
bitcoin-data:
litecoin-data:
litecoin-mweb-data:
monero-wallet-rpc-data:

View File

@@ -6,7 +6,6 @@ ARG CHECKSUM_SHA512
ENV ELECTRUM_VERSION=$VERSION
ENV ELECTRUM_USER=electrum
ENV ELECTRUM_HOME=/home/$ELECTRUM_USER
ENV ELECTRUM_NETWORK=mainnet
RUN adduser -D $ELECTRUM_USER
@@ -32,10 +31,12 @@ RUN mkdir -p /data \
ln -sf ${ELECTRUM_HOME}/.electrum/ /data && \
chown -R ${ELECTRUM_USER} ${ELECTRUM_HOME}/.electrum /data
USER $ELECTRUM_USER
WORKDIR $ELECTRUM_HOME
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
USER $BITCOIN_ELECTRUM_USER
WORKDIR $BITCOIN_ELECTRUM_HOME
VOLUME /data
EXPOSE 7000
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

View File

@@ -3,9 +3,9 @@ set -ex
trap 'pkill -TERM -P1; electrum stop; exit 0' SIGTERM
rm -f .electrum/daemon
electrum --offline setconfig rpcuser ${ELECTRUM_USER}
electrum --offline setconfig rpcpassword ${ELECTRUM_PASSWORD}
rm -f .electrum/daemon .electrum/daemon_rpc_socket
electrum --offline setconfig rpcuser ${ELECTRUM_RPC_USER}
electrum --offline setconfig rpcpassword ${ELECTRUM_RPC_PASSWORD}
electrum --offline setconfig rpchost 0.0.0.0
electrum --offline setconfig rpcport 7000

View File

@@ -0,0 +1,25 @@
FROM ubuntu:latest
ARG VERSION
ARG CHECKSUM_SHA512
RUN apt update && \
apt install -y wget libfuse2 fuse3
ENV ELECTRUM_VERSION=$VERSION
ENV ELECTRUM_USER=electrum
ENV ELECTRUM_HOME=/home/$ELECTRUM_USER
ENV ELECTRUM_CHECKSUM_SHA512 $CHECKSUM_SHA512
RUN useradd -m $ELECTRUM_USER
USER $ELECTRUM_USER
WORKDIR $ELECTRUM_HOME
RUN wget https://github.com/ltcmweb/electrum-ltc/releases/download/${ELECTRUM_VERSION}/electrum-ltc-${ELECTRUM_VERSION}-x86_64.AppImage -O electrum-ltc.appimage
RUN [ "${ELECTRUM_CHECKSUM_SHA512} electrum-ltc.appimage" = "$(sha512sum electrum-ltc.appimage)" ]
RUN echo -e "**************************\n SHA 512 Checksum OK\n**************************"
COPY --chown=$ELECTRUM_USER:$ELECTRUM_USER ./docker-entrypoint.sh docker-entrypoint.sh
RUN chmod +x ./electrum-ltc.appimage ./docker-entrypoint.sh
EXPOSE 7000
ENTRYPOINT [ "./docker-entrypoint.sh" ]

View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
trap 'pkill -TERM -P1; electrum-ltc.appimage stop; exit 0' SIGTERM
rm -f $HOME/.electrum-ltc/daemon
./electrum-ltc.appimage --offline setconfig rpcuser ${ELECTRUM_RPC_USER}
./electrum-ltc.appimage --offline setconfig rpcpassword ${ELECTRUM_RPC_PASSWORD}
./electrum-ltc.appimage --offline setconfig rpchost 0.0.0.0
./electrum-ltc.appimage --offline setconfig rpcport 7000
if [ -n "${ELECTRUM_SERVER_ADDRESS}" ]; then
./electrum-ltc.appimage daemon -1 -s "${ELECTRUM_SERVER_ADDRESS}" "$@"
else
./electrum-ltc.appimage daemon "$@"
fi

26
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "asn1crypto"
@@ -6,6 +6,7 @@ version = "1.5.1"
description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
{file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
@@ -17,6 +18,7 @@ version = "2.9.3"
description = "Generation of mnemonics, seeds, private/public keys and addresses for different types of cryptocurrencies"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "bip_utils-2.9.3-py3-none-any.whl", hash = "sha256:ee26b8417a576c7f89b847da37316db01a5cece1994c1609d37fbeefb91ad45e"},
{file = "bip_utils-2.9.3.tar.gz", hash = "sha256:72a8c95484b57e92311b0b2a3d5195b0ce4395c19a0b157d4a289e8b1300f48a"},
@@ -41,6 +43,7 @@ version = "5.6.4"
description = "CBOR (de)serializer with extensive tag support"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "cbor2-5.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c40c68779a363f47a11ded7b189ba16767391d5eae27fac289e7f62b730ae1fc"},
{file = "cbor2-5.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0625c8d3c487e509458459de99bf052f62eb5d773cc9fc141c6a6ea9367726d"},
@@ -83,7 +86,7 @@ files = [
[package.extras]
benchmarks = ["pytest-benchmark (==4.0.0)"]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)", "typing-extensions"]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)", "typing-extensions ; python_version < \"3.12\""]
test = ["coverage (>=7)", "hypothesis", "pytest"]
[[package]]
@@ -92,6 +95,7 @@ version = "2024.8.30"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
@@ -103,6 +107,7 @@ version = "1.17.1"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
@@ -182,6 +187,7 @@ version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
groups = ["main"]
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
@@ -281,6 +287,7 @@ version = "20.0.0"
description = "Cross-platform Python CFFI bindings for libsecp256k1"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "coincurve-20.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d559b22828638390118cae9372a1bb6f6594f5584c311deb1de6a83163a0919b"},
{file = "coincurve-20.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33d7f6ebd90fcc550f819f7f2cce2af525c342aac07f0ccda46ad8956ad9d99b"},
@@ -347,6 +354,7 @@ version = "1.7"
description = "CRC Generator"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"},
]
@@ -357,6 +365,7 @@ version = "0.19.0"
description = "ECDSA cryptographic signature library (pure python)"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6"
groups = ["main"]
files = [
{file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"},
{file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"},
@@ -375,6 +384,7 @@ version = "1.4.1"
description = "Ed25519 public-key signatures (BLAKE2b fork)"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "ed25519-blake2b-1.4.1.tar.gz", hash = "sha256:731e9f93cd1ac1a64649575f3519a99ffe0bb1e4cf7bf5f5f0be513a39df7363"},
]
@@ -385,6 +395,7 @@ version = "3.8"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"},
{file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"},
@@ -396,6 +407,7 @@ version = "0.2.0"
description = "Python bindings for sr25519 library"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:86cc1a571852a4f2ade827ebf211e066b23ab805d3e864cbe213a3d8cd53f7d5"},
{file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:453c9088e39dd04b07bf3ada6c473a5349c4dfd965009a35124b2c807117eda8"},
@@ -477,6 +489,7 @@ version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
@@ -488,6 +501,7 @@ version = "3.20.0"
description = "Cryptographic library for Python"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
groups = ["main"]
files = [
{file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"},
{file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"},
@@ -529,6 +543,7 @@ version = "1.5.0"
description = "Python binding to the Networking and Cryptography (NaCl) library"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
@@ -555,6 +570,7 @@ version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
@@ -576,6 +592,7 @@ version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
groups = ["main"]
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@@ -587,18 +604,19 @@ version = "2.2.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
{file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[metadata]
lock-version = "2.0"
lock-version = "2.1"
python-versions = "^3.12"
content-hash = "312bfc0d67e89feee1abbdbabc31649ff525b844d43eeaa06d57b9e9d26fbf06"

View File

@@ -1,16 +1,16 @@
[tool.poetry]
name = "magic-autoforward-autoconvert"
version = "0.1.0"
version = "0.2.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
requests = "^2.32.3"
bip-utils = "^2.9.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -6,12 +6,14 @@ import time
import util
import env
# https://support.kraken.com/hc/en-us/articles/205893708-Minimum-order-size-volume-for-trading
order_min = {
'XBT': 0.0001,
'LTC': 0.05,
'XMR': 0.03
}
def get_balance(asset: Literal['XBT', 'XMR']) -> str:
def get_balance(asset: Literal['XBT', 'LTC', 'XMR']) -> str:
balances = util.kraken_request('/0/private/Balance')
balance = '0'
@@ -20,10 +22,10 @@ def get_balance(asset: Literal['XBT', 'XMR']) -> str:
return balance
def get_bids(asset: Literal['XBT', 'XMR']):
def get_bids(asset: Literal['XBT', 'LTC', 'XMR']):
return util.kraken_request('/0/public/Depth', {'pair': f'{asset}USD'})[f'X{asset}ZUSD']['bids']
def attempt_sell(asset: Literal['XBT', 'XMR']):
def attempt_sell(asset: Literal['XBT', 'LTC', 'XMR']):
balance = float(get_balance(asset))
to_sell_amount = 0
@@ -65,9 +67,9 @@ def attempt_sell(asset: Literal['XBT', 'XMR']):
print(util.get_time(), f'Not selling {asset} due to high slippage.')
while 1:
for asset in ['XBT', 'XMR']:
for asset in ['XBT', 'LTC', 'XMR']:
try:
attempt_sell(cast(Literal['XBT', 'XMR'], asset))
attempt_sell(cast(Literal['XBT', 'LTC', 'XMR'], asset))
except Exception as e:
print(util.get_time(), f'Error attempting to sell {asset}:')
print(traceback.format_exc())

View File

@@ -4,31 +4,51 @@ import traceback
import requests
import json
from constants import MIN_BITCOIN_SEND_AMOUNT, MIN_MONERO_SEND_AMOUNT
from constants import MIN_BITCOIN_SEND_AMOUNT, MIN_LITECOIN_SEND_AMOUNT, MIN_MONERO_SEND_AMOUNT
import util
import env
def get_bitcoin_fee_rate() -> int:
return requests.get('https://mempool.space/api/v1/fees/recommended').json()['halfHourFee']
ElectrumCoin = Literal['btc', 'ltc', 'ltc-mweb']
def set_bitcoin_fee_rate(rate: int):
util.request_electrum_rpc('setconfig', ['dynamic_fees', False])
util.request_electrum_rpc('setconfig', ['fee_per_kb', rate * 1000])
def get_fee_rate(coin: ElectrumCoin) -> int:
coin_to_source: dict[ElectrumCoin, str] = {
'btc': env.BITCOIN_FEE_SOURCE,
'ltc': env.LITECOIN_FEE_SOURCE,
'ltc-mweb': env.LITECOIN_FEE_SOURCE
}
def get_bitcoin_balance() -> float:
return float(util.request_electrum_rpc('getbalance')['confirmed'])
coin_to_rate: dict[ElectrumCoin, str] = {
'btc': env.BITCOIN_FEE_RATE,
'ltc': env.LITECOIN_FEE_RATE,
'ltc-mweb': env.LITECOIN_FEE_RATE
}
def create_psbt(destination_address: str) -> str:
source = coin_to_source[coin]
rate = coin_to_rate[coin]
return requests.get(source).json()[rate]
def set_electrum_fee_rate(coin: ElectrumCoin, rate: int, dynamic: bool):
if dynamic: # Fall back to the Electrum rate if there is an issue
util.request_electrum_rpc(coin, 'setconfig', ['dynamic_fees', True])
else:
util.request_electrum_rpc(coin, 'setconfig', ['dynamic_fees', False])
util.request_electrum_rpc(coin, 'setconfig', ['fee_per_kb', rate * 1000])
def get_electrum_balance(coin: ElectrumCoin) -> float:
return float(util.request_electrum_rpc(coin, 'getbalance')['confirmed'])
def create_psbt(coin: ElectrumCoin, destination_address: str, unsigned = True) -> str:
params = {
'destination': destination_address,
'amount': '!',
'unsigned': True # This way we can get the input amounts
'unsigned': unsigned # This way we can get the input amounts
}
return util.request_electrum_rpc('payto', params)
return util.request_electrum_rpc(coin, 'payto', params)
def get_psbt_data(psbt: str) -> dict:
return util.request_electrum_rpc('deserialize', [psbt])
def get_psbt_data(coin: ElectrumCoin, psbt: str) -> dict:
return util.request_electrum_rpc(coin, 'deserialize', [psbt])
def get_total_psbt_fee(psbt_data: dict) -> float:
inputs_sum_sats = 0
@@ -41,18 +61,18 @@ def get_total_psbt_fee(psbt_data: dict) -> float:
outputs_sum_sats += cast(int, _output['value_sats'])
total_fee_sats = inputs_sum_sats - outputs_sum_sats
total_fee_btc = total_fee_sats / 100000000
return total_fee_btc
total_fee = total_fee_sats / 100000000
return total_fee
def sign_psbt(psbt: str) -> str:
return cast(str, util.request_electrum_rpc('signtransaction', [psbt]))
def sign_psbt(coin: ElectrumCoin, psbt: str) -> str:
return cast(str, util.request_electrum_rpc(coin, 'signtransaction', [psbt]))
def broadcast_bitcoin_tx(signed_tx: str):
util.request_electrum_rpc('broadcast', [signed_tx])
def broadcast_electrum_tx(coin: ElectrumCoin, signed_tx: str):
util.request_electrum_rpc(coin, 'broadcast', [signed_tx])
def get_monero_balance() -> float:
params = {'account_index': 0}
return util.request_monero_rpc('get_balance', params)['balance'] / 1000000000000
return util.request_monero_rpc('get_balance', params)['unlocked_balance'] / 1000000000000
def sweep_all_monero(address: str) -> None:
params = {
@@ -62,72 +82,91 @@ def sweep_all_monero(address: str) -> None:
util.request_monero_rpc('sweep_all', params)
def get_new_kraken_address(asset: Literal['XBT', 'XMR']) -> str:
payload = {
def get_new_kraken_address(asset: Literal['btc', 'ltc', 'xmr']) -> str:
sym_to_name = {
"btc": "Bitcoin",
"ltc": "Litecoin",
"xmr": "Monero"
}
payload: dict[str, str | bool] = {
'asset': asset,
'method': 'Bitcoin' if asset == 'XBT' else 'Monero'
'method': sym_to_name[asset]
}
result = util.kraken_request('/0/private/DepositAddresses', payload)
first_new_address = next((addr for addr in result if addr.get('new', False)), None)
if first_new_address:
return(first_new_address['address'])
return first_new_address['address']
else:
payload = {
'asset': asset,
'method': 'Bitcoin' if asset == 'XBT' else 'Monero',
'new': True
}
payload['new'] = True
result = util.kraken_request('/0/private/DepositAddresses', payload)
first_new_address = next((addr for addr in result if addr.get('new', False)), None)
return(first_new_address['address'])
if first_new_address:
return first_new_address['address']
raise Exception(f'Kraken did not return a new address: {json.dumps(result, indent=2)}')
def attempt_bitcoin_autoforward():
balance = get_bitcoin_balance()
def attempt_electrum_autoforward(coin: ElectrumCoin):
coin_upper = coin.upper()
balance = get_electrum_balance(coin)
coin_to_min_send: dict[ElectrumCoin, float] = {
'btc': MIN_BITCOIN_SEND_AMOUNT,
'ltc': MIN_LITECOIN_SEND_AMOUNT,
'ltc-mweb': MIN_LITECOIN_SEND_AMOUNT
}
min_send = coin_to_min_send[coin]
if balance < MIN_BITCOIN_SEND_AMOUNT:
print(util.get_time(), f'Not enough Bitcoin balance to autoforward. (Balance: {balance}, Min Send: {MIN_BITCOIN_SEND_AMOUNT})')
print(util.get_time(), f'Not enough {coin_upper} balance to autoforward. (Balance: {balance}, Min. send: {min_send})')
return
fee_rate = get_bitcoin_fee_rate()
set_bitcoin_fee_rate(fee_rate)
address = get_new_kraken_address('XBT')
try:
psbt = create_psbt(address)
except requests.exceptions.HTTPError as http_error:
response_json = cast(dict, http_error.response.json())
fee_rate = get_fee_rate(coin)
set_electrum_fee_rate(coin, fee_rate, dynamic=False)
except:
set_electrum_fee_rate(coin, rate=0, dynamic=True)
address = get_new_kraken_address(coin if coin != 'ltc-mweb' else 'ltc')
if response_json.get('error', {}).get('data', {}).get('exception', '') == 'NotEnoughFunds()':
print(util.get_time(), f'Not autoforwarding due to high transaction fee.')
return
# Electrum-ltc doesn't support deserializing mweb transactions, so we can't check total fee
if coin != 'ltc-mweb':
try:
psbt = create_psbt(coin, address)
except requests.exceptions.HTTPError as http_error:
response_json = cast(dict, http_error.response.json())
raise http_error
if response_json.get('error', {}).get('data', {}).get('exception', '') == 'NotEnoughFunds()':
print(util.get_time(), f'Not autoforwarding due to high transaction fee.')
return
psbt_data = get_psbt_data(psbt)
total_fee = get_total_psbt_fee(psbt_data)
amount = balance
raise http_error
if total_fee / amount * 100 > env.MAX_BITCOIN_FEE_PERCENT:
print(util.get_time(), f'Not autoforwarding due to high transaction fee.')
return
psbt_data = get_psbt_data(coin, psbt)
total_fee = get_total_psbt_fee(psbt_data)
amount = balance
signed_tx = sign_psbt(psbt)
broadcast_bitcoin_tx(signed_tx)
if total_fee / amount * 100 > env.MAX_NETWORK_FEE_PERCENT:
print(util.get_time(), f'Not autoforwarding due to high transaction fee ({total_fee} {coin_upper}).')
return
print(util.get_time(), f'Autoforwarded {amount} BTC to {address}!')
signed_tx = sign_psbt(coin, psbt)
else:
signed_tx = create_psbt(coin, address, unsigned=False)
broadcast_electrum_tx(coin, signed_tx)
print(util.get_time(), f'Autoforwarded {amount} {coin_upper} to {address}!')
def attempt_monero_autoforward():
balance = get_monero_balance()
if balance < MIN_MONERO_SEND_AMOUNT:
print(util.get_time(), f'Not enough Monero balance to autoforward. (Balance: {balance}, Min Send: {MIN_MONERO_SEND_AMOUNT})')
print(util.get_time(), f'Not enough XMR balance to autoforward. (Balance: {balance}, Min Send: {MIN_MONERO_SEND_AMOUNT})')
return
address = get_new_kraken_address('XMR')
address = get_new_kraken_address('xmr')
sweep_all_monero(address)
print(util.get_time(), f'Autoforwarded {balance} XMR to {address}!')
@@ -136,15 +175,27 @@ util.wait_for_wallets()
while 1:
try:
attempt_bitcoin_autoforward()
attempt_electrum_autoforward('btc')
except Exception as e:
print(util.get_time(), 'Error autoforwarding bitcoin:')
print(util.get_time(), 'Error autoforwarding Bitcoin:')
print(traceback.format_exc())
try:
attempt_electrum_autoforward('ltc')
except Exception as e:
print(util.get_time(), 'Error autoforwarding litecoin:')
print(traceback.format_exc())
# try:
# attempt_electrum_autoforward('ltc-mweb')
# except Exception as e:
# print(util.get_time(), 'Error autoforwarding Litecoin MWEB:')
# print(traceback.format_exc())
try:
attempt_monero_autoforward()
except Exception as e:
print(util.get_time(), 'Error autoforwarding monero:')
print(util.get_time(), 'Error autoforwarding Monero:')
print(traceback.format_exc())
sleep(60 * 5)

View File

@@ -1,2 +1,3 @@
MIN_BITCOIN_SEND_AMOUNT = 0.0001
MIN_MONERO_SEND_AMOUNT = 0.01
MIN_LITECOIN_SEND_AMOUNT = 0.05
MIN_MONERO_SEND_AMOUNT = 0.03

View File

@@ -1,19 +1,31 @@
import os
ELECTRUM_RPC_URL = os.getenv('ELECTRUM_RPC_URL', 'http://electrs:7000')
BITCOIN_ELECTRUM_RPC_URL = os.getenv('BITCOIN_ELECTRUM_RPC_URL', '')
LITECOIN_ELECTRUM_RPC_URL = os.getenv('LITECOIN_ELECTRUM_RPC_URL', '')
LITECOIN_MWEB_ELECTRUM_RPC_URL = os.getenv('LITECOIN_MWEB_ELECTRUM_RPC_URL', '')
ELECTRUM_RPC_USERNAME = os.getenv('ELECTRUM_RPC_USERNAME', '')
ELECTRUM_RPC_PASSWORD = os.getenv('ELECTRUM_RPC_PASSWORD', '')
BITCOIN_ELECTRUM_SERVER_ADDRESS = os.getenv('BITCOIN_ELECTRUM_SERVER_ADDRESS', '')
LITECOIN_ELECTRUM_SERVER_ADDRESS = os.getenv('LITECOIN_ELECTRUM_SERVER_ADDRESS', '')
BITCOIN_WALLET_SEED = os.getenv('BITCOIN_WALLET_SEED', '')
LITECOIN_WALLET_SEED = os.getenv('LITECOIN_WALLET_SEED', '')
LITECOIN_MWEB_WALLET_SEED = os.getenv('LITECOIN_MWEB_WALLET_SEED', '')
MONERO_RPC_URL = os.getenv('MONERO_RPC_URL', 'http://monero-wallet-rpc:18082/json_rpc')
MONERO_RPC_URL = os.getenv('MONERO_RPC_URL', '')
MONERO_RPC_USERNAME = os.getenv('MONERO_RPC_USERNAME', '')
MONERO_RPC_PASSWORD = os.getenv('MONERO_RPC_PASSWORD', '')
MONERO_WALLET_SEED = os.getenv('MONERO_WALLET_SEED', '')
MONERO_WALLET_PASSWORD = os.getenv('MONERO_WALLET_PASSWORD', '')
MONERO_WALLET_HEIGHT = os.getenv('MONERO_WALLET_HEIGHT', '')
MONERO_DAEMON_ADDRESS = os.getenv('MONERO_DAEMON_ADDRESS', '')
KRAKEN_API_KEY = os.getenv('KRAKEN_API_KEY', '')
KRAKEN_API_SECRET = os.getenv('KRAKEN_API_SECRET', '')
MAX_BITCOIN_FEE_PERCENT = float(os.getenv('MAX_BITCOIN_FEE_PERCENT', '10'))
MAX_SLIPPAGE_PERCENT = float(os.getenv('MAX_SLIPPAGE_PERCENT', '1'))
MAX_NETWORK_FEE_PERCENT = float(os.getenv('MAX_NETWORK_FEE_PERCENT', '5'))
MAX_SLIPPAGE_PERCENT = float(os.getenv('MAX_SLIPPAGE_PERCENT', '0.5'))
BITCOIN_FEE_SOURCE = os.getenv('BITCOIN_FEE_SOURCE', 'https://mempool.space/api/v1/fees/recommended')
BITCOIN_FEE_RATE = os.getenv('BITCOIN_FEE_RATE', 'halfHourFee')
LITECOIN_FEE_SOURCE = os.getenv('LITECOIN_FEE_SOURCE', 'https://litecoinspace.org/api/v1/fees/recommended')
LITECOIN_FEE_RATE = os.getenv('LITECOIN_FEE_RATE', 'halfHourFee')

View File

@@ -1,18 +1,27 @@
from typing import Literal
from bip_utils import Bip39SeedGenerator, Bip84, Bip84Coins
import traceback
import util
import env
def get_zprv_from_seed(mnemonic: str) -> str:
def get_xprv_from_mnemonic(coin: Literal['btc', 'ltc',], mnemonic: str ) -> str:
coin_type = Bip84Coins.BITCOIN if coin == 'btc' else Bip84Coins.LITECOIN
seed_bytes = Bip39SeedGenerator(mnemonic).Generate()
bip84_master_key = Bip84.FromSeed(seed_bytes, Bip84Coins.BITCOIN)
bip84_master_key = Bip84.FromSeed(seed_bytes, coin_type)
zprv = bip84_master_key.Purpose().Coin().Account(0).PrivateKey().ToExtended()
return zprv
def import_bitcoin_seed():
zprv = get_zprv_from_seed(env.BITCOIN_WALLET_SEED)
util.request_electrum_rpc('restore', [zprv])
xprv = get_xprv_from_mnemonic('btc', env.BITCOIN_WALLET_SEED)
util.request_electrum_rpc('btc', 'restore', [xprv])
def import_litecoin_seed():
xprv = get_xprv_from_mnemonic('ltc', env.LITECOIN_WALLET_SEED)
util.request_electrum_rpc('ltc', 'restore', [xprv])
def import_litecoin_mweb_seed():
util.request_electrum_rpc('ltc-mweb', 'restore', [env.LITECOIN_MWEB_WALLET_SEED])
def import_monero_seed():
params = {
@@ -30,13 +39,31 @@ util.wait_for_rpc()
try:
import_bitcoin_seed()
util.request_electrum_rpc('load_wallet')
util.request_electrum_rpc('changegaplimit', [1000, 'iknowhatimdoing'])
util.request_electrum_rpc('btc', 'load_wallet')
util.request_electrum_rpc('btc', 'changegaplimit', [1000, 'iknowhatimdoing'])
print('Bitcoin seed has successfully been imported!')
except Exception as e:
print(util.get_time(), 'Error importing bitcoin seed:')
print(traceback.format_exc())
try:
import_litecoin_seed()
util.request_electrum_rpc('ltc', 'load_wallet')
util.request_electrum_rpc('ltc', 'changegaplimit', [1000, 'iknowhatimdoing'])
print('Litecoin seed has successfully been imported!')
except Exception as e:
print(util.get_time(), 'Error importing litecoin seed:')
print(traceback.format_exc())
# try:
# import_litecoin_mweb_seed()
# util.request_electrum_rpc('ltc-mweb', 'load_wallet')
# util.request_electrum_rpc('ltc-mweb', 'changegaplimit', [1000, 'iknowhatimdoing'])
# print('Litecoin mimblewimble seed has successfully been imported!')
# except Exception as e:
# print(util.get_time(), 'Error importing litecoin mimblewimble seed:')
# print(traceback.format_exc())
try:
import_monero_seed()
print('Monero seed has successfully been imported!')

View File

@@ -1,4 +1,5 @@
import ast
from typing import Literal, cast
from requests.auth import HTTPDigestAuth
from datetime import datetime
import urllib.parse
@@ -14,9 +15,21 @@ import env
def get_time() -> str:
return f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]'
def request_electrum_rpc(method: str, params: list | dict = []):
def request_electrum_rpc(coin: Literal['btc', 'ltc', 'ltc-mweb'], method: str, params: list | dict = []):
headers = {'content-type': 'application/json'}
coin_to_auth = {
'btc': (env.ELECTRUM_RPC_USERNAME, env.ELECTRUM_RPC_PASSWORD),
'ltc': (env.ELECTRUM_RPC_USERNAME, env.ELECTRUM_RPC_PASSWORD),
'ltc-mweb': (env.ELECTRUM_RPC_USERNAME, env.ELECTRUM_RPC_PASSWORD)
}
coin_to_url = {
'btc': env.BITCOIN_ELECTRUM_RPC_URL,
'ltc': env.LITECOIN_ELECTRUM_RPC_URL,
'ltc-mweb': env.LITECOIN_MWEB_ELECTRUM_RPC_URL
}
data = {
'jsonrpc': '2.0',
'id': 'curltext',
@@ -25,10 +38,10 @@ def request_electrum_rpc(method: str, params: list | dict = []):
}
response = requests.post(
env.ELECTRUM_RPC_URL,
coin_to_url[coin],
headers=headers,
data=json.dumps(data),
auth=(env.ELECTRUM_RPC_USERNAME, env.ELECTRUM_RPC_PASSWORD)
auth=coin_to_auth[coin]
)
response_json = response.json()
@@ -67,18 +80,33 @@ def request_monero_rpc(method: str, params: dict = {}):
return response_json['result']
def open_bitcoin_wallet():
request_electrum_rpc('load_wallet')
request_electrum_rpc('btc', 'load_wallet')
def open_litecoin_wallet():
request_electrum_rpc('ltc', 'load_wallet')
def open_litecoin_mweb_wallet():
request_electrum_rpc('ltc-mweb', 'load_wallet')
def open_monero_wallet() -> None:
params = {'filename': 'foo', 'password': env.MONERO_WALLET_PASSWORD}
request_monero_rpc('open_wallet', params)
def wait_for_rpc():
print('Waiting for Electrum RPC...')
print('Waiting for Bitcoin Electrum RPC...')
while 1:
try:
request_electrum_rpc('getinfo')
request_electrum_rpc('btc', 'getinfo')
break
except:
time.sleep(10)
print('Waiting for Litecoin Electrum RPC...')
while 1:
try:
request_electrum_rpc('ltc', 'getinfo')
break
except:
time.sleep(10)
@@ -87,13 +115,15 @@ def wait_for_rpc():
while 1:
try:
request_electrum_rpc('getinfo')
request_monero_rpc('get_height')
break
except:
except Exception as e:
if 'No wallet file' in e.__str__():
break
time.sleep(10)
def wait_for_wallets():
print('Waiting for Electrum wallet...')
print('Waiting for Bitcoin wallet...')
while 1:
try:
@@ -101,6 +131,24 @@ def wait_for_wallets():
break
except:
time.sleep(10)
print('Waiting for Litecoin wallet...')
while 1:
try:
open_litecoin_wallet()
break
except:
time.sleep(10)
# print('Waiting for Litecoin MWEB wallet...')
# while 1:
# try:
# open_litecoin_mweb_wallet()
# break
# except:
# time.sleep(10)
print('Waiting for Monero wallet...')