mirror of
https://github.com/MAGICGrants/autoforward-autoconvert.git
synced 2026-01-09 21:48:04 -05:00
20
.env.example
20
.env.example
@@ -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"
|
||||
35
.github/workflows/deploy.yml
vendored
35
.github/workflows/deploy.yml
vendored
@@ -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
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
16
README.md
16
README.md
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
6
electrum-client/docker-entrypoint.sh → docker/btc-electrum/docker-entrypoint.sh
Executable file → Normal file
6
electrum-client/docker-entrypoint.sh → docker/btc-electrum/docker-entrypoint.sh
Executable file → Normal 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
|
||||
|
||||
25
docker/ltc-electrum/Dockerfile
Normal file
25
docker/ltc-electrum/Dockerfile
Normal 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" ]
|
||||
18
docker/ltc-electrum/docker-entrypoint.sh
Normal file
18
docker/ltc-electrum/docker-entrypoint.sh
Normal 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
26
poetry.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
20
src/env.py
20
src/env.py
@@ -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')
|
||||
@@ -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!')
|
||||
|
||||
66
src/util.py
66
src/util.py
@@ -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...')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user