From bbf4ee777d7b7fb796fdb9832addee070d2d9861 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer <12520755+SamsungGalaxyPlayer@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:31:17 -0500 Subject: [PATCH 01/18] feat: add autoforward script --- .gitignore | 1 + autoforward.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 .gitignore create mode 100644 autoforward.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b694934 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.venv \ No newline at end of file diff --git a/autoforward.py b/autoforward.py new file mode 100644 index 0000000..102125b --- /dev/null +++ b/autoforward.py @@ -0,0 +1,133 @@ +import requests +import json +from time import sleep +from datetime import datetime +from requests.auth import HTTPDigestAuth + +BITCOIN_RPC_URL = os.getenv('BITCOIN_RPC_URL') +BITCOIN_RPC_USERNAME = os.getenv('BITCOIN_RPC_USERNAME') +BITCOIN_RPC_PASSWORD = os.getenv('BITCOIN_RPC_PASSWORD') +MONERO_RPC_URL = os.getenv('MONERO_RPC_URL') +MONERO_RPC_USERNAME = os.getenv('MONERO_RPC_USERNAME') +MONERO_RPC_PASSWORD = os.getenv('MONERO_RPC_PASSWORD') +KRAKEN_API_KEY = os.getenv('KRAKEN_API_KEY') +KRAKEN_API_SECRET = os.getenv('KRAKEN_API_SECRET') + +def get_time() -> str: + return f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]' + +def get_kraken_signature(url: str, payload: dict): + postdata = urllib.parse.urlencode(payload) + encoded = (str(payload['nonce']) + postdata).encode() + message = url.encode() + hashlib.sha256(encoded).digest() + mac = hmac.new(base64.b64decode(KRAKEN_API_SECRET), message, hashlib.sha512) + sigdigest = base64.b64encode(mac.digest()) + return sigdigest.decode() + +def kraken_request(path: str, payload: dict) -> requests.Request: + headers = {} + headers['API-Key'] = KRAKEN_API_KEY + headers['API-Sign'] = get_kraken_signature(path, payload) + + return requests.post( + 'https://api.kraken.com/0' + path, + headers=headers, + data=payload + ).json()['result'] + +def request_bitcoin_rpc(method: str, params: list[str] = []) -> any: + headers = {'content-type': 'application/json'} + + data = { + 'jsonrpc': '1.0', + 'id': 'python-bitcoin', + 'method': 'getbalance', + 'params': params, + } + + return requests.post( + BITCOIN_RPC_URL, + headers=headers, + data=json.dumps(data), + auth=(BITCOIN_RPC_USERNAME, BITCOIN_RPC_PASSWORD) + ).json()['result'] + +def request_monero_rpc(method: str, params: dict[str, any] = {}) -> any: + headers = {'content-type': 'application/json'} + + data = { + 'jsonrpc': '2.0', + 'id': '0', + 'method': method, + 'params': params + } + + return requests.post( + MONERO_RPC_URL, + headers=headers, + json=data, + auth=HTTPDigestAuth(MONERO_RPC_USERNAME, MONERO_RPC_PASSWORD) + ).json()['result'] + +def get_bitcoin_fee_rate() -> int: + return requests.get('https://mempool.space/api/v1/fees/recommended').json()['halfHourFee'] + +def get_bitcoin_balance() -> float: + return request_bitcoin_rpc('getbalance') + +def send_bitcoin(address: str, amount: float, fee_rate: int) -> None: + # https://bitcoincore.org/en/doc/24.0.0/rpc/wallet/sendtoaddress/ + params = [address, amount, null, null, true, true, null, null, null, fee_rate] + request_bitcoin_rpc('sendtoaddress', params) + +def get_monero_balance() -> float: + params = {'account_index': 0} + + return request_monero_rpc('get_balance', params)['balance'] + +def sweep_all_monero(address: str) -> None: + params = { + 'account_index': 0, + 'address': address, + } + + request_monero_rpc('sweep_all', params) + +def get_new_kraken_address(asset: 'XBT' | 'XMR') -> str: + payload = { + 'asset': asset, + 'method': 'Bitcoin' if asset == 'XBT' else 'Monero' + } + + result = kraken_request('/private/DepositAddresses', payload) + adddress = next((address['address'] for address in result['result'] if address['new']), None) + + return address + +while 1: + try: + balance = get_bitcoin_balance() + + if balance > 0: + fee_rate = get_bitcoin_fee_rate() + address = get_new_kraken_address('XBT') + amount = balance + send_bitcoin(address, amount, fee_rate) + print(get_time(), f'Sent {amount} BTC to {address}!') + else: + print(get_time(), 'No bitcoin balance to sweep.') + except Exception as e: + print(get_time(), 'Error autoforwarding Bitcoin:', e) + + try: + balance = get_monero_balance() + + if balance > 0: + address = get_new_kraken_address('XMR') + sweep_all_monero(address) + else: + print(get_time(), 'No bitcoin balance to sweep.') + except Exception as e: + print(get_time(), 'Error autoforwarding Monero:', e) + + sleep(60 * 5) From 57cf7dce5ef77e210e48bb2c03f7901b91173950 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 2 Sep 2024 16:15:44 -0300 Subject: [PATCH 02/18] feat: add autoconvert --- .env.example | 8 ++++++ .gitignore | 3 +- autoconvert.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ autoforward.py | 28 ++----------------- helpers.py | 31 +++++++++++++++++++++ 5 files changed, 118 insertions(+), 26 deletions(-) create mode 100644 .env.example create mode 100644 autoconvert.py create mode 100644 helpers.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7ead704 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +BITCOIN_RPC_URL="" +BITCOIN_RPC_USERNAME="" +BITCOIN_RPC_PASSWORD="" +MONERO_RPC_URL="" +MONERO_RPC_USERNAME="" +MONERO_RPC_PASSWORD="" +KRAKEN_API_KEY="" +KRAKEN_API_SECRET="" \ No newline at end of file diff --git a/.gitignore b/.gitignore index b694934..c2eabec 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.venv \ No newline at end of file +.venv +.env \ No newline at end of file diff --git a/autoconvert.py b/autoconvert.py new file mode 100644 index 0000000..b6560ec --- /dev/null +++ b/autoconvert.py @@ -0,0 +1,74 @@ +import time +import os + +from helpers import * + +MAX_SLIPPAGE_PERCENT = 1 + +order_min = { + 'XBT': 0.0001, + 'XMR': 0.03 +} + +def get_balance(asset: 'XBT' | 'XMR') -> str: + balances = kraken_request('/0/private/Balance') + balance = '0' + + if f'X{asset}' in balances: + balance = balances[f'X{asset}'] + + return balance + +def get_bids(asset: 'XBT' | 'XMR'): + return kraken_request('/0/public/Depth', {pair: f'{asset}USD'})[f'X{asset}ZUSD']['bids'] + +def attempt_sell(asset: 'XBT' | 'XMR'): + balance = float(get_balance(asset)) + to_sell_amount = 0 + + if balance < order_min[asset]: + print(get_time(), f'No enough {asset} balance to sell.') + return + + bids = get_bids() + market_price = float(bids[0][0]) + + for bid in bids: + bid_price = float(bid[0]) # Example 161.05 + bid_volume = float(bid[1]) + slippage_percent = ((market_price - bid_price) / market_price) * 100 # Example ((161.06-161.05)/161.06)*100 = 0.0062088662610207 + + # Break loop if slippage is too high + if slippage_percent > MAX_SLIPPAGE_PERCENT: + break + + # Otherwise, add the bid_volume to the to_sell_amount + to_sell_amount += bid_volume + + if to_sell_amount > balance: + to_sell_amount = balance + + # Sell asset if amount is greater than the minimum order amount + # Otherwise, it means that we could not get to the minimum amount under the max slippage + if to_sell_amount > order_min[asset]: + payload = { + 'ordertype': 'market', + 'type': 'sell', + 'pair': f'{asset}USD', + 'volume': to_sell_amount, + } + + kraken_request('/0/private/AddOrder', payload) + print(get_time(), f'Sold {to_sell_amount} {asset}!') + else: + print(get_time(), f'Not selling {asset} due to high slippage.') + +while 1: + for asset in ['XBT', 'XMR']: + try: + attempt_sell(asset) + except Exception as e: + print(get_time(), f'Error attempting to sell {asset}:', e) + + delay = random.randint(30, 90) + time.sleep(delay) \ No newline at end of file diff --git a/autoforward.py b/autoforward.py index 102125b..94abfca 100644 --- a/autoforward.py +++ b/autoforward.py @@ -4,36 +4,14 @@ from datetime import datetime from requests.auth import HTTPDigestAuth +from helpers import * + BITCOIN_RPC_URL = os.getenv('BITCOIN_RPC_URL') BITCOIN_RPC_USERNAME = os.getenv('BITCOIN_RPC_USERNAME') BITCOIN_RPC_PASSWORD = os.getenv('BITCOIN_RPC_PASSWORD') MONERO_RPC_URL = os.getenv('MONERO_RPC_URL') MONERO_RPC_USERNAME = os.getenv('MONERO_RPC_USERNAME') MONERO_RPC_PASSWORD = os.getenv('MONERO_RPC_PASSWORD') -KRAKEN_API_KEY = os.getenv('KRAKEN_API_KEY') -KRAKEN_API_SECRET = os.getenv('KRAKEN_API_SECRET') - -def get_time() -> str: - return f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]' - -def get_kraken_signature(url: str, payload: dict): - postdata = urllib.parse.urlencode(payload) - encoded = (str(payload['nonce']) + postdata).encode() - message = url.encode() + hashlib.sha256(encoded).digest() - mac = hmac.new(base64.b64decode(KRAKEN_API_SECRET), message, hashlib.sha512) - sigdigest = base64.b64encode(mac.digest()) - return sigdigest.decode() - -def kraken_request(path: str, payload: dict) -> requests.Request: - headers = {} - headers['API-Key'] = KRAKEN_API_KEY - headers['API-Sign'] = get_kraken_signature(path, payload) - - return requests.post( - 'https://api.kraken.com/0' + path, - headers=headers, - data=payload - ).json()['result'] def request_bitcoin_rpc(method: str, params: list[str] = []) -> any: headers = {'content-type': 'application/json'} @@ -99,7 +77,7 @@ def get_new_kraken_address(asset: 'XBT' | 'XMR') -> str: 'method': 'Bitcoin' if asset == 'XBT' else 'Monero' } - result = kraken_request('/private/DepositAddresses', payload) + result = kraken_request('/0/private/DepositAddresses', payload) adddress = next((address['address'] for address in result['result'] if address['new']), None) return address diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..5b4ecc0 --- /dev/null +++ b/helpers.py @@ -0,0 +1,31 @@ +import urllib +import hashlib +import hmac +import base64 +import requests + +KRAKEN_API_KEY = os.getenv('KRAKEN_API_KEY') +KRAKEN_API_SECRET = os.getenv('KRAKEN_API_SECRET') + +def get_time() -> str: + return f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]' + +def get_kraken_signature(url: str, payload: dict): + postdata = urllib.parse.urlencode(payload) + encoded = (str(payload['nonce']) + postdata).encode() + message = url.encode() + hashlib.sha256(encoded).digest() + mac = hmac.new(base64.b64decode(KRAKEN_API_SECRET), message, hashlib.sha512) + sigdigest = base64.b64encode(mac.digest()) + return sigdigest.decode() + +def kraken_request(path: str, payload = {}) -> requests.Request: + payload['nonce'] = str(int(1000*time.time())) + headers = {} + headers['API-Key'] = KRAKEN_API_KEY + headers['API-Sign'] = get_kraken_signature(path, payload) + + return requests.post( + 'https://api.kraken.com' + path, + headers=headers, + data=payload + ).json()['result'] \ No newline at end of file From e3125c193403ea5e027a62999472ab1cc7eea5a2 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 9 Sep 2024 12:44:31 -0300 Subject: [PATCH 03/18] feat: use electrum rpc for bitcoin autoforward, refactoring and add deploy files --- .env.example | 16 ++- .github/workflows/deploy.yml | 42 ++++++++ .gitignore | 3 +- autoforward.py | 111 -------------------- docker-compose.yml | 85 +++++++++++++++ electrum-client/Dockerfile | 41 ++++++++ electrum-client/docker-entrypoint.sh | 11 ++ helpers.py | 31 ------ requirements.txt | 2 + autoconvert.py => src/autoconvert.py | 17 +-- src/autoforward.py | 149 +++++++++++++++++++++++++++ src/constants.py | 3 + src/env.py | 15 +++ src/seed-importer.py | 36 +++++++ src/util.py | 86 ++++++++++++++++ 15 files changed, 493 insertions(+), 155 deletions(-) create mode 100644 .github/workflows/deploy.yml delete mode 100644 autoforward.py create mode 100644 docker-compose.yml create mode 100644 electrum-client/Dockerfile create mode 100755 electrum-client/docker-entrypoint.sh delete mode 100644 helpers.py create mode 100644 requirements.txt rename autoconvert.py => src/autoconvert.py (81%) create mode 100644 src/autoforward.py create mode 100644 src/constants.py create mode 100644 src/env.py create mode 100644 src/seed-importer.py create mode 100644 src/util.py diff --git a/.env.example b/.env.example index 7ead704..f096ed8 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,16 @@ -BITCOIN_RPC_URL="" -BITCOIN_RPC_USERNAME="" -BITCOIN_RPC_PASSWORD="" +ELECTRS_DAEMON_RPC_ADDR="" +ELECTRS_DAEMON_P2P_ADDR="" + +ELECTRUM_RPC_URL="" +ELECTRUM_RPC_PASSWORD="" +BITCOIN_WALLET_SEED="" + MONERO_RPC_URL="" MONERO_RPC_USERNAME="" MONERO_RPC_PASSWORD="" +MONERO_WALLET_SEED="" +MONERO_WALLET_PASSWORD="" +MONERO_WALLET_HEIGHT="" + KRAKEN_API_KEY="" -KRAKEN_API_SECRET="" \ No newline at end of file +KRAKEN_API_SECRET="" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..3fe0d7c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,42 @@ +name: Deploy autoforward and autoconvert programs + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + environment: production + + steps: + - uses: actions/checkout@v4 + - uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Deploy + run: | + ssh -o StrictHostKeyChecking=no ${{ secrets.VPS_USER }}@${{ secrets.VPS_IP }} << 'EOF' + export HISTFILE=/dev/null + cd autoforward-autoconvert + git checkout main + echo "Pulling changes..." + git pull + echo "Starting..." + + ELECTRS_DAEMON_RPC_ADDR=${{ secrets.ELECTRS_DAEMON_RPC_ADDR }} \ + ELECTRS_DAEMON_P2P_ADDR=${{ secrets.ELECTRS_DAEMON_P2P_ADDR }} \ + ELECTRUM_RPC_URL=${{ secrets.ELECTRUM_RPC_URL }} \ + ELECTRUM_RPC_PASSWORD=${{ secrets.ELECTRUM_RPC_PASSWORD }} \ + BITCOIN_WALLET_SEED=${{ secrets.BITCOIN_WALLET_SEED }} \ + MONERO_RPC_URL=${{ secrets.MONERO_RPC_URL }} \ + MONERO_RPC_USERNAME=${{ secrets.MONERO_RPC_USERNAME }} \ + 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 }} \ + docker compose up -d --build + EOF diff --git a/.gitignore b/.gitignore index c2eabec..74df079 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .venv -.env \ No newline at end of file +.env +.electrs-cookie \ No newline at end of file diff --git a/autoforward.py b/autoforward.py deleted file mode 100644 index 94abfca..0000000 --- a/autoforward.py +++ /dev/null @@ -1,111 +0,0 @@ -import requests -import json -from time import sleep -from datetime import datetime -from requests.auth import HTTPDigestAuth - -from helpers import * - -BITCOIN_RPC_URL = os.getenv('BITCOIN_RPC_URL') -BITCOIN_RPC_USERNAME = os.getenv('BITCOIN_RPC_USERNAME') -BITCOIN_RPC_PASSWORD = os.getenv('BITCOIN_RPC_PASSWORD') -MONERO_RPC_URL = os.getenv('MONERO_RPC_URL') -MONERO_RPC_USERNAME = os.getenv('MONERO_RPC_USERNAME') -MONERO_RPC_PASSWORD = os.getenv('MONERO_RPC_PASSWORD') - -def request_bitcoin_rpc(method: str, params: list[str] = []) -> any: - headers = {'content-type': 'application/json'} - - data = { - 'jsonrpc': '1.0', - 'id': 'python-bitcoin', - 'method': 'getbalance', - 'params': params, - } - - return requests.post( - BITCOIN_RPC_URL, - headers=headers, - data=json.dumps(data), - auth=(BITCOIN_RPC_USERNAME, BITCOIN_RPC_PASSWORD) - ).json()['result'] - -def request_monero_rpc(method: str, params: dict[str, any] = {}) -> any: - headers = {'content-type': 'application/json'} - - data = { - 'jsonrpc': '2.0', - 'id': '0', - 'method': method, - 'params': params - } - - return requests.post( - MONERO_RPC_URL, - headers=headers, - json=data, - auth=HTTPDigestAuth(MONERO_RPC_USERNAME, MONERO_RPC_PASSWORD) - ).json()['result'] - -def get_bitcoin_fee_rate() -> int: - return requests.get('https://mempool.space/api/v1/fees/recommended').json()['halfHourFee'] - -def get_bitcoin_balance() -> float: - return request_bitcoin_rpc('getbalance') - -def send_bitcoin(address: str, amount: float, fee_rate: int) -> None: - # https://bitcoincore.org/en/doc/24.0.0/rpc/wallet/sendtoaddress/ - params = [address, amount, null, null, true, true, null, null, null, fee_rate] - request_bitcoin_rpc('sendtoaddress', params) - -def get_monero_balance() -> float: - params = {'account_index': 0} - - return request_monero_rpc('get_balance', params)['balance'] - -def sweep_all_monero(address: str) -> None: - params = { - 'account_index': 0, - 'address': address, - } - - request_monero_rpc('sweep_all', params) - -def get_new_kraken_address(asset: 'XBT' | 'XMR') -> str: - payload = { - 'asset': asset, - 'method': 'Bitcoin' if asset == 'XBT' else 'Monero' - } - - result = kraken_request('/0/private/DepositAddresses', payload) - adddress = next((address['address'] for address in result['result'] if address['new']), None) - - return address - -while 1: - try: - balance = get_bitcoin_balance() - - if balance > 0: - fee_rate = get_bitcoin_fee_rate() - address = get_new_kraken_address('XBT') - amount = balance - send_bitcoin(address, amount, fee_rate) - print(get_time(), f'Sent {amount} BTC to {address}!') - else: - print(get_time(), 'No bitcoin balance to sweep.') - except Exception as e: - print(get_time(), 'Error autoforwarding Bitcoin:', e) - - try: - balance = get_monero_balance() - - if balance > 0: - address = get_new_kraken_address('XMR') - sweep_all_monero(address) - else: - print(get_time(), 'No bitcoin balance to sweep.') - except Exception as e: - print(get_time(), 'Error autoforwarding Monero:', e) - - sleep(60 * 5) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..54fe4c9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,85 @@ +services: + electrum-client: + build: + context: ./electrum-client + args: + VERSION: "4.5.5" + CHECKSUM_SHA512: "3bdfce2187466fff20fd67736bdf257bf95d3517de47043be411ccda558a442b8fd81d6a8da094a39a1db39a7339dcd4282e73a7f00cf6bbd70473d7ce456b0b" + container_name: electrum-client + restart: unless-stopped + environment: + - ELECTRUM_USER=user + - ELECTRUM_PASSWORD=${ELECTRUM_RPC_PASSWORD} + ports: + - 7000:7000 + + monero-wallet-rpc: + image: sethsimmons/simple-monero-wallet-rpc:latest + restart: unless-stopped + container_name: monero-wallet-rpc + volumes: + - monero-wallet-rpc-data:/home/monero + command: + - "--trusted-daemon" + - "--rpc-bind-port=18082" + - "--rpc-login=monero:monero" + - "--daemon-address=xmr.tcpcat.net:18089" + - "--wallet-dir=/home/monero/wallet" + - "--log-level=4" + + seed-importer: + image: curlimages/curl + container_name: seed-importer + environment: + - ELECTRUM_RPC_URL=${ELECTRUM_RPC_URL} + - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} + - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} + - MONERO_RPC_URL=${MONERO_RPC_URL} + - MONERO_RPC_USERNAME=${MONERO_RPC_USERNAME} + - MONERO_RPC_PASSWORD=${MONERO_RPC_PASSWORD} + - MONERO_WALLET_SEED=${MONERO_WALLET_SEED} + - MONERO_WALLET_PASSWORD=${MONERO_WALLET_PASSWORD} + - MONERO_WALLET_HEIGHT=${MONERO_WALLET_HEIGHT} + volumes: + - ./src/seed-importer.sh:/script/seed-importer.sh + command: /bin/sh /script/seed-importer.sh + depends_on: + - electrum_client + - monero-wallet-rpc + + autoforward: + image: python:3.12-alpine + container_name: autoforward + restart: unless-stopped + environment: + - ELECTRUM_RPC_URL=${ELECTRUM_RPC_URL} + - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} + - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} + - MONERO_RPC_URL=${MONERO_RPC_URL} + - MONERO_RPC_USERNAME=${MONERO_RPC_USERNAME} + - 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} + volumes: + - ./requirements.txt:/app/requirements.txt + - ./src:/app/src + command: pip install -r /app/requirements.txt && python /app/src/autoforward.py + + autoconvert: + image: python:3.12-alpine + container_name: autoconvert + restart: unless-stopped + environment: + - KRAKEN_API_KEY=${KRAKEN_API_KEY} + - KRAKEN_API_SECRET=${KRAKEN_API_SECRET} + volumes: + - ./requirements.txt:/app/requirements.txt + - ./src:/app/src + command: pip install -r /app/requirements.txt && python /app/src/autoconvert.py + +volumes: + electrs_data: + monero-wallet-rpc-data: diff --git a/electrum-client/Dockerfile b/electrum-client/Dockerfile new file mode 100644 index 0000000..15b5f91 --- /dev/null +++ b/electrum-client/Dockerfile @@ -0,0 +1,41 @@ +FROM python:3.12-alpine + +ARG VERSION +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 + +RUN mkdir -p /data ${ELECTRUM_HOME} && \ + ln -sf /data ${ELECTRUM_HOME}/.electrum && \ + chown ${ELECTRUM_USER} ${ELECTRUM_HOME}/.electrum /data + +# IMPORTANT: always verify gpg signature before changing a hash here! +ENV ELECTRUM_CHECKSUM_SHA512 $CHECKSUM_SHA512 + +RUN apk --no-cache add --virtual build-dependencies gcc musl-dev libsecp256k1 libsecp256k1-dev libressl-dev +RUN wget https://download.electrum.org/${ELECTRUM_VERSION}/Electrum-${ELECTRUM_VERSION}.tar.gz +RUN [ "${ELECTRUM_CHECKSUM_SHA512} Electrum-${ELECTRUM_VERSION}.tar.gz" = "$(sha512sum Electrum-${ELECTRUM_VERSION}.tar.gz)" ] +RUN echo -e "**************************\n SHA 512 Checksum OK\n**************************" +RUN pip3 install cryptography Electrum-${ELECTRUM_VERSION}.tar.gz +RUN rm -f Electrum-${ELECTRUM_VERSION}.tar.gz + +RUN mkdir -p /data \ + ${ELECTRUM_HOME}/.electrum/wallets/ \ + ${ELECTRUM_HOME}/.electrum/testnet/wallets/ \ + ${ELECTRUM_HOME}/.electrum/regtest/wallets/ \ + ${ELECTRUM_HOME}/.electrum/simnet/wallets/ && \ + ln -sf ${ELECTRUM_HOME}/.electrum/ /data && \ + chown -R ${ELECTRUM_USER} ${ELECTRUM_HOME}/.electrum /data + +USER $ELECTRUM_USER +WORKDIR $ELECTRUM_HOME +VOLUME /data +EXPOSE 7000 + +COPY docker-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/electrum-client/docker-entrypoint.sh b/electrum-client/docker-entrypoint.sh new file mode 100755 index 0000000..cbe3e67 --- /dev/null +++ b/electrum-client/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +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} +electrum --offline setconfig rpchost 0.0.0.0 +electrum --offline setconfig rpcport 7000 +electrum daemon "$@" \ No newline at end of file diff --git a/helpers.py b/helpers.py deleted file mode 100644 index 5b4ecc0..0000000 --- a/helpers.py +++ /dev/null @@ -1,31 +0,0 @@ -import urllib -import hashlib -import hmac -import base64 -import requests - -KRAKEN_API_KEY = os.getenv('KRAKEN_API_KEY') -KRAKEN_API_SECRET = os.getenv('KRAKEN_API_SECRET') - -def get_time() -> str: - return f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]' - -def get_kraken_signature(url: str, payload: dict): - postdata = urllib.parse.urlencode(payload) - encoded = (str(payload['nonce']) + postdata).encode() - message = url.encode() + hashlib.sha256(encoded).digest() - mac = hmac.new(base64.b64decode(KRAKEN_API_SECRET), message, hashlib.sha512) - sigdigest = base64.b64encode(mac.digest()) - return sigdigest.decode() - -def kraken_request(path: str, payload = {}) -> requests.Request: - payload['nonce'] = str(int(1000*time.time())) - headers = {} - headers['API-Key'] = KRAKEN_API_KEY - headers['API-Sign'] = get_kraken_signature(path, payload) - - return requests.post( - 'https://api.kraken.com' + path, - headers=headers, - data=payload - ).json()['result'] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a7be810 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests +bip_utils \ No newline at end of file diff --git a/autoconvert.py b/src/autoconvert.py similarity index 81% rename from autoconvert.py rename to src/autoconvert.py index b6560ec..00f02b2 100644 --- a/autoconvert.py +++ b/src/autoconvert.py @@ -1,7 +1,8 @@ import time -import os +import random +from typing import Literal, cast -from helpers import * +from util import * MAX_SLIPPAGE_PERCENT = 1 @@ -10,7 +11,7 @@ 'XMR': 0.03 } -def get_balance(asset: 'XBT' | 'XMR') -> str: +def get_balance(asset: Literal['XBT', 'XMR']) -> str: balances = kraken_request('/0/private/Balance') balance = '0' @@ -19,10 +20,10 @@ def get_balance(asset: 'XBT' | 'XMR') -> str: return balance -def get_bids(asset: 'XBT' | 'XMR'): - return kraken_request('/0/public/Depth', {pair: f'{asset}USD'})[f'X{asset}ZUSD']['bids'] +def get_bids(asset: Literal['XBT', 'XMR']): + return kraken_request('/0/public/Depth', {'pair': f'{asset}USD'})[f'X{asset}ZUSD']['bids'] -def attempt_sell(asset: 'XBT' | 'XMR'): +def attempt_sell(asset: Literal['XBT', 'XMR']): balance = float(get_balance(asset)) to_sell_amount = 0 @@ -30,7 +31,7 @@ def attempt_sell(asset: 'XBT' | 'XMR'): print(get_time(), f'No enough {asset} balance to sell.') return - bids = get_bids() + bids = get_bids(asset) market_price = float(bids[0][0]) for bid in bids: @@ -66,7 +67,7 @@ def attempt_sell(asset: 'XBT' | 'XMR'): while 1: for asset in ['XBT', 'XMR']: try: - attempt_sell(asset) + attempt_sell(cast(Literal['XBT', 'XMR'], asset)) except Exception as e: print(get_time(), f'Error attempting to sell {asset}:', e) diff --git a/src/autoforward.py b/src/autoforward.py new file mode 100644 index 0000000..68cafe0 --- /dev/null +++ b/src/autoforward.py @@ -0,0 +1,149 @@ +from typing import Literal, cast +from time import sleep +import requests +import json + +from constants import MAX_BITCOIN_FEE_PERCENT, MIN_BITCOIN_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'] + +def open_bitcoin_wallet(): + util.request_electrum_rpc('open_wallet') + +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_bitcoin_balance() -> float: + return float(util.request_electrum_rpc('getbalance')['confirmed']) + +def create_psbt(destination_address: str) -> str: + params = { + 'destination': destination_address, + 'amount': '!', + 'unsigned': True # This way we can get the input amounts + } + + response = util.request_electrum_rpc('payto', params) + return response['result'] + +def get_psbt_data(psbt: str) -> dict: + return util.request_electrum_rpc('deserialize', [psbt]) + +def get_total_psbt_fee(psbt_data: dict) -> float: + inputs_sum_sats = 0 + outputs_sum_sats = 0 + + for _input in psbt_data['inputs']: + inputs_sum_sats += cast(int, _input['value_sats']) + + for _output in psbt_data['outputs']: + 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 + +def sign_psbt(psbt: str) -> str: + return cast(str, util.request_electrum_rpc('signtransaction', [psbt])) + +def broadcast_bitcoin_tx(signed_tx: str): + util.request_electrum_rpc('broadcast', [signed_tx]) + +def open_monero_wallet() -> None: + params = {'filename': 'wallet', 'password': env.MONERO_WALLET_PASSWORD} + util.request_monero_rpc('open_wallet', params) + +def get_monero_balance() -> float: + params = {'account_index': 0} + return util.request_monero_rpc('get_balance', params)['balance'] / 1000000000000 + +def sweep_all_monero(address: str) -> None: + params = { + 'account_index': 0, + 'address': address, + } + + util.request_monero_rpc('sweep_all', params) + +def get_new_kraken_address(asset: Literal['XBT', 'XMR']) -> str: + payload = { + 'asset': asset, + 'method': 'Bitcoin' if asset == 'XBT' else 'Monero' + } + + result = util.kraken_request('/0/private/DepositAddresses', payload) + + for address in result['result']: + if address['new']: + return address['address'] + + raise Exception(f'Kraken did not return a new address: {json.dumps(result, indent=2)}') + +def attempt_bitcoin_autoforward(): + open_bitcoin_wallet() + balance = get_bitcoin_balance() + + if balance < MIN_BITCOIN_SEND_AMOUNT: + print(util.get_time(), 'No enough bitcoin balance to autoforward.') + 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()) + + if response_json.get('error', {}).get('data', {}).get('exception', '') == 'NotEnoughFunds()': + print(util.get_time(), f'Not autoforwarding due to high transaction fee.') + return + + raise http_error + + if psbt == None: + 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 + + if total_fee / amount * 100 > MAX_BITCOIN_FEE_PERCENT: + print(util.get_time(),f'Not autoforwarding due to high transaction fee.') + return + + signed_tx = sign_psbt(psbt) + broadcast_bitcoin_tx(signed_tx) + + print(util.get_time(), f'Autoforwarded {amount} BTC to {address}!') + +def attempt_monero_autoforward(): + balance = get_monero_balance() + + if balance < MIN_MONERO_SEND_AMOUNT: + print(util.get_time(), 'No enough monero balance to autoforward.') + return + + address = get_new_kraken_address('XMR') + sweep_all_monero(address) + print(util.get_time(), f'Autoforwarded {balance} XMR to {address}!') + +while 1: + try: + attempt_bitcoin_autoforward() + except Exception as e: + print(util.get_time(), 'Error autoforwarding bitcoin:', e) + + try: + attempt_monero_autoforward() + except Exception as e: + print(util.get_time(), 'Error autoforwarding monero:', e) + + sleep(60 * 5) + diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..09bb41d --- /dev/null +++ b/src/constants.py @@ -0,0 +1,3 @@ +MIN_BITCOIN_SEND_AMOUNT = 0.0001 +MAX_BITCOIN_FEE_PERCENT = 10 +MIN_MONERO_SEND_AMOUNT = 0.01 \ No newline at end of file diff --git a/src/env.py b/src/env.py new file mode 100644 index 0000000..ca45478 --- /dev/null +++ b/src/env.py @@ -0,0 +1,15 @@ +import os + +ELECTRUM_RPC_URL = os.getenv('ELECTRUM_RPC_URL', 'http://electrs:7000') +ELECTRUM_RPC_PASSWORD = os.getenv('ELECTRUM_RPC_PASSWORD', '') +BITCOIN_WALLET_SEED = os.getenv('BITCOIN_WALLET_SEED', '') + +MONERO_RPC_URL = os.getenv('MONERO_RPC_URL', 'http://monero-wallet-rpc:18082/json_rpc') +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', '') + +KRAKEN_API_KEY = os.getenv('KRAKEN_API_KEY', '') +KRAKEN_API_SECRET = os.getenv('KRAKEN_API_SECRET', '') diff --git a/src/seed-importer.py b/src/seed-importer.py new file mode 100644 index 0000000..186b5a6 --- /dev/null +++ b/src/seed-importer.py @@ -0,0 +1,36 @@ +from bip_utils import Bip39SeedGenerator, Bip84, Bip84Coins + +import util +import env + +def get_zprv_from_seed(mnemonic: str) -> str: + seed_bytes = Bip39SeedGenerator(mnemonic).Generate() + bip84_master_key = Bip84.FromSeed(seed_bytes, Bip84Coins.BITCOIN) + 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]) + +def import_monero_seed(): + monero_params = { + 'filename': 'wallet', + 'seed': env.MONERO_WALLET_SEED, + 'password': env.MONERO_RPC_PASSWORD, + 'restore_height': env.MONERO_WALLET_HEIGHT, + 'language': 'english', + 'autosave_current': True + } + + util.request_monero_rpc('restore_deterministic_wallet', monero_params) + +try: + import_bitcoin_seed() +except Exception as e: + print(util.get_time(), 'Error importing bitcoin seed:', e) + +try: + import_monero_seed() +except Exception as e: + print(util.get_time(), 'Error importing monero seed:', e) \ No newline at end of file diff --git a/src/util.py b/src/util.py new file mode 100644 index 0000000..2415e7b --- /dev/null +++ b/src/util.py @@ -0,0 +1,86 @@ +from requests.auth import HTTPDigestAuth +from datetime import datetime +import urllib.parse +import requests +import hashlib +import base64 +import hmac +import json +import time + +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 = []) -> dict: + headers = {'content-type': 'application/json'} + + data = { + 'jsonrpc': '2.0', + 'id': '0', + 'method': method, + 'params': params, + } + + response = requests.post( + env.ELECTRUM_RPC_URL, + headers=headers, + data=json.dumps(data), + auth=('user', env.ELECTRUM_RPC_PASSWORD) + ) + + response_json = response.json() + + if 'error' in response_json: + response.status_code = 400 + + response.raise_for_status() + + return response_json['result'] + +def request_monero_rpc(method: str, params: dict = {}) -> dict: + headers = {'content-type': 'application/json'} + + data = { + 'jsonrpc': '2.0', + 'id': '0', + 'method': method, + 'params': params + } + + response = requests.post( + env.MONERO_RPC_URL, + headers=headers, + json=data, + auth=HTTPDigestAuth(env.MONERO_RPC_USERNAME, env.MONERO_RPC_PASSWORD) + ) + + response_json = response.json() + + if 'error' in response_json: + response.status_code = 400 + + response.raise_for_status() + + return response_json['result'] + +def get_kraken_signature(url: str, payload: dict): + postdata = urllib.parse.urlencode(payload) + encoded = (str(payload['nonce']) + postdata).encode() + message = url.encode() + hashlib.sha256(encoded).digest() + mac = hmac.new(base64.b64decode(env.KRAKEN_API_SECRET), message, hashlib.sha512) + sigdigest = base64.b64encode(mac.digest()) + return sigdigest.decode() + +def kraken_request(path: str, payload = {}) -> dict: + payload['nonce'] = str(int(1000*time.time())) + headers = {} + headers['API-Key'] = env.KRAKEN_API_KEY + headers['API-Sign'] = get_kraken_signature(path, payload) + + return requests.post( + 'https://api.kraken.com' + path, + headers=headers, + data=payload + ).json()['result'] \ No newline at end of file From 67f76fdbc011bb2b9805ee3ac86355c9a979a047 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 9 Sep 2024 16:58:54 -0300 Subject: [PATCH 04/18] feat: improvements --- .env.example | 11 +++++------ Dockerfile | 13 +++++++++++++ docker-compose.yml | 43 ++++++++++++++++++++++--------------------- src/autoconvert.py | 22 ++++++++++++---------- src/autoforward.py | 27 +++++++++++++-------------- src/env.py | 1 + src/seed-importer.py | 9 +++++++-- src/util.py | 27 +++++++++++++++++---------- 8 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 Dockerfile diff --git a/.env.example b/.env.example index f096ed8..d613d58 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,11 @@ -ELECTRS_DAEMON_RPC_ADDR="" -ELECTRS_DAEMON_P2P_ADDR="" - -ELECTRUM_RPC_URL="" +ELECTRUM_RPC_URL="http://electrum-client:7000" +ELECTRUM_RPC_USERNAME="user" ELECTRUM_RPC_PASSWORD="" BITCOIN_WALLET_SEED="" -MONERO_RPC_URL="" -MONERO_RPC_USERNAME="" +MONERO_DAEMON_ADDRESS="xmr.tcpcat.net:18089" +MONERO_RPC_URL="http://monero-wallet-rpc:18082/json_rpc" +MONERO_RPC_USERNAME="user" MONERO_RPC_PASSWORD="" MONERO_WALLET_SEED="" MONERO_WALLET_PASSWORD="" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f1896f0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.12-alpine + +RUN apk add --no-cache build-base musl-dev gcc +ENV VIRTUAL_ENV=/opt/venv +RUN python3 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + +WORKDIR /app + +# Install dependencies: +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY src src diff --git a/docker-compose.yml b/docker-compose.yml index 54fe4c9..3cd822b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,10 +8,8 @@ services: container_name: electrum-client restart: unless-stopped environment: - - ELECTRUM_USER=user + - ELECTRUM_USER=${ELECTRUM_RPC_USERNAME} - ELECTRUM_PASSWORD=${ELECTRUM_RPC_PASSWORD} - ports: - - 7000:7000 monero-wallet-rpc: image: sethsimmons/simple-monero-wallet-rpc:latest @@ -22,16 +20,19 @@ services: command: - "--trusted-daemon" - "--rpc-bind-port=18082" - - "--rpc-login=monero:monero" - - "--daemon-address=xmr.tcpcat.net:18089" + - "--rpc-login=${MONERO_RPC_USERNAME}:${MONERO_RPC_PASSWORD}" + - "--daemon-address=${MONERO_DAEMON_ADDRESS}" - "--wallet-dir=/home/monero/wallet" - "--log-level=4" seed-importer: - image: curlimages/curl + build: + context: . container_name: seed-importer environment: + - PYTHONUNBUFFERED=1 - ELECTRUM_RPC_URL=${ELECTRUM_RPC_URL} + - ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME} - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} - MONERO_RPC_URL=${MONERO_RPC_URL} @@ -40,19 +41,20 @@ services: - MONERO_WALLET_SEED=${MONERO_WALLET_SEED} - MONERO_WALLET_PASSWORD=${MONERO_WALLET_PASSWORD} - MONERO_WALLET_HEIGHT=${MONERO_WALLET_HEIGHT} - volumes: - - ./src/seed-importer.sh:/script/seed-importer.sh - command: /bin/sh /script/seed-importer.sh + command: python /app/src/seed-importer.py depends_on: - - electrum_client + - electrum-client - monero-wallet-rpc autoforward: - image: python:3.12-alpine + build: + context: . container_name: autoforward restart: unless-stopped environment: + - PYTHONUNBUFFERED=1 - ELECTRUM_RPC_URL=${ELECTRUM_RPC_URL} + - ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME} - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} - MONERO_RPC_URL=${MONERO_RPC_URL} @@ -63,23 +65,22 @@ services: - MONERO_WALLET_HEIGHT=${MONERO_WALLET_HEIGHT} - KRAKEN_API_KEY=${KRAKEN_API_KEY} - KRAKEN_API_SECRET=${KRAKEN_API_SECRET} - volumes: - - ./requirements.txt:/app/requirements.txt - - ./src:/app/src - command: pip install -r /app/requirements.txt && python /app/src/autoforward.py + command: python /app/src/autoforward.py + depends_on: + - seed-importer autoconvert: - image: python:3.12-alpine + build: + context: . container_name: autoconvert restart: unless-stopped environment: + - PYTHONUNBUFFERED=1 - KRAKEN_API_KEY=${KRAKEN_API_KEY} - KRAKEN_API_SECRET=${KRAKEN_API_SECRET} - volumes: - - ./requirements.txt:/app/requirements.txt - - ./src:/app/src - command: pip install -r /app/requirements.txt && python /app/src/autoconvert.py + command: python /app/src/autoconvert.py + depends_on: + - seed-importer volumes: - electrs_data: monero-wallet-rpc-data: diff --git a/src/autoconvert.py b/src/autoconvert.py index 00f02b2..2b6a312 100644 --- a/src/autoconvert.py +++ b/src/autoconvert.py @@ -1,8 +1,9 @@ -import time -import random from typing import Literal, cast +import traceback +import random +import time -from util import * +import util MAX_SLIPPAGE_PERCENT = 1 @@ -12,7 +13,7 @@ } def get_balance(asset: Literal['XBT', 'XMR']) -> str: - balances = kraken_request('/0/private/Balance') + balances = util.kraken_request('/0/private/Balance') balance = '0' if f'X{asset}' in balances: @@ -21,14 +22,14 @@ def get_balance(asset: Literal['XBT', 'XMR']) -> str: return balance def get_bids(asset: Literal['XBT', 'XMR']): - return kraken_request('/0/public/Depth', {'pair': f'{asset}USD'})[f'X{asset}ZUSD']['bids'] + return util.kraken_request('/0/public/Depth', {'pair': f'{asset}USD'})[f'X{asset}ZUSD']['bids'] def attempt_sell(asset: Literal['XBT', 'XMR']): balance = float(get_balance(asset)) to_sell_amount = 0 if balance < order_min[asset]: - print(get_time(), f'No enough {asset} balance to sell.') + print(util.get_time(), f'No enough {asset} balance to sell.') return bids = get_bids(asset) @@ -59,17 +60,18 @@ def attempt_sell(asset: Literal['XBT', 'XMR']): 'volume': to_sell_amount, } - kraken_request('/0/private/AddOrder', payload) - print(get_time(), f'Sold {to_sell_amount} {asset}!') + util.kraken_request('/0/private/AddOrder', payload) + print(util.get_time(), f'Sold {to_sell_amount} {asset}!') else: - print(get_time(), f'Not selling {asset} due to high slippage.') + print(util.get_time(), f'Not selling {asset} due to high slippage.') while 1: for asset in ['XBT', 'XMR']: try: attempt_sell(cast(Literal['XBT', 'XMR'], asset)) except Exception as e: - print(get_time(), f'Error attempting to sell {asset}:', e) + print(util.get_time(), f'Error attempting to sell {asset}:') + print(traceback.format_exc()) delay = random.randint(30, 90) time.sleep(delay) \ No newline at end of file diff --git a/src/autoforward.py b/src/autoforward.py index 68cafe0..f2de1a4 100644 --- a/src/autoforward.py +++ b/src/autoforward.py @@ -1,5 +1,6 @@ from typing import Literal, cast from time import sleep +import traceback import requests import json @@ -11,7 +12,7 @@ def get_bitcoin_fee_rate() -> int: return requests.get('https://mempool.space/api/v1/fees/recommended').json()['halfHourFee'] def open_bitcoin_wallet(): - util.request_electrum_rpc('open_wallet') + util.request_electrum_rpc('load_wallet') def set_bitcoin_fee_rate(rate: int): util.request_electrum_rpc('setconfig', ['dynamic_fees', False]) @@ -27,8 +28,7 @@ def create_psbt(destination_address: str) -> str: 'unsigned': True # This way we can get the input amounts } - response = util.request_electrum_rpc('payto', params) - return response['result'] + return util.request_electrum_rpc('payto', params) def get_psbt_data(psbt: str) -> dict: return util.request_electrum_rpc('deserialize', [psbt]) @@ -77,7 +77,7 @@ def get_new_kraken_address(asset: Literal['XBT', 'XMR']) -> str: result = util.kraken_request('/0/private/DepositAddresses', payload) - for address in result['result']: + for address in result: if address['new']: return address['address'] @@ -93,7 +93,8 @@ def attempt_bitcoin_autoforward(): fee_rate = get_bitcoin_fee_rate() set_bitcoin_fee_rate(fee_rate) - address = get_new_kraken_address('XBT') + # REMOVE THIS ADDRESS + address = 'bc1qwqp0fczemr7044vas6a36nn57ecvs7a7cmferj' or get_new_kraken_address('XBT') try: psbt = create_psbt(address) @@ -106,20 +107,16 @@ def attempt_bitcoin_autoforward(): raise http_error - if psbt == None: - 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 if total_fee / amount * 100 > MAX_BITCOIN_FEE_PERCENT: - print(util.get_time(),f'Not autoforwarding due to high transaction fee.') + print(util.get_time(), f'Not autoforwarding due to high transaction fee.') return signed_tx = sign_psbt(psbt) - broadcast_bitcoin_tx(signed_tx) + # broadcast_bitcoin_tx(signed_tx) print(util.get_time(), f'Autoforwarded {amount} BTC to {address}!') @@ -130,7 +127,7 @@ def attempt_monero_autoforward(): print(util.get_time(), 'No enough monero balance to autoforward.') return - address = get_new_kraken_address('XMR') + address = '88PJJSQ4NYNZ4kFNu516GMgkjX1w7B7FwAdh2PwFJakb4JXYUVeJqt21WSCiccqvSLA2NwRkSMeiXfgpcrmmwr4rQM9Yubq' or get_new_kraken_address('XMR') sweep_all_monero(address) print(util.get_time(), f'Autoforwarded {balance} XMR to {address}!') @@ -138,12 +135,14 @@ def attempt_monero_autoforward(): try: attempt_bitcoin_autoforward() except Exception as e: - print(util.get_time(), 'Error autoforwarding bitcoin:', e) + print(util.get_time(), 'Error autoforwarding bitcoin:') + print(traceback.format_exc()) try: attempt_monero_autoforward() except Exception as e: - print(util.get_time(), 'Error autoforwarding monero:', e) + print(util.get_time(), 'Error autoforwarding monero:') + print(traceback.format_exc()) sleep(60 * 5) diff --git a/src/env.py b/src/env.py index ca45478..fa436c7 100644 --- a/src/env.py +++ b/src/env.py @@ -1,6 +1,7 @@ import os ELECTRUM_RPC_URL = os.getenv('ELECTRUM_RPC_URL', 'http://electrs:7000') +ELECTRUM_RPC_USERNAME = os.getenv('ELECTRUM_RPC_USERNAME', '') ELECTRUM_RPC_PASSWORD = os.getenv('ELECTRUM_RPC_PASSWORD', '') BITCOIN_WALLET_SEED = os.getenv('BITCOIN_WALLET_SEED', '') diff --git a/src/seed-importer.py b/src/seed-importer.py index 186b5a6..9c8df19 100644 --- a/src/seed-importer.py +++ b/src/seed-importer.py @@ -1,4 +1,5 @@ from bip_utils import Bip39SeedGenerator, Bip84, Bip84Coins +import traceback import util import env @@ -27,10 +28,14 @@ def import_monero_seed(): try: import_bitcoin_seed() + print('Bitcoin seed has successfully been imported!') except Exception as e: - print(util.get_time(), 'Error importing bitcoin seed:', e) + print(util.get_time(), 'Error importing bitcoin seed:') + print(traceback.format_exc()) try: import_monero_seed() + print('Monero seed has successfully been imported!') except Exception as e: - print(util.get_time(), 'Error importing monero seed:', e) \ No newline at end of file + print(util.get_time(), 'Error importing monero seed:') + print(traceback.format_exc()) diff --git a/src/util.py b/src/util.py index 2415e7b..954f300 100644 --- a/src/util.py +++ b/src/util.py @@ -13,12 +13,12 @@ def get_time() -> str: return f'[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]' -def request_electrum_rpc(method: str, params: list | dict = []) -> dict: +def request_electrum_rpc(method: str, params: list | dict = []): headers = {'content-type': 'application/json'} data = { 'jsonrpc': '2.0', - 'id': '0', + 'id': 'curltext', 'method': method, 'params': params, } @@ -27,19 +27,19 @@ def request_electrum_rpc(method: str, params: list | dict = []) -> dict: env.ELECTRUM_RPC_URL, headers=headers, data=json.dumps(data), - auth=('user', env.ELECTRUM_RPC_PASSWORD) + auth=(env.ELECTRUM_RPC_USERNAME, env.ELECTRUM_RPC_PASSWORD) ) response_json = response.json() if 'error' in response_json: - response.status_code = 400 + raise Exception(response_json) response.raise_for_status() return response_json['result'] -def request_monero_rpc(method: str, params: dict = {}) -> dict: +def request_monero_rpc(method: str, params: dict = {}): headers = {'content-type': 'application/json'} data = { @@ -59,7 +59,7 @@ def request_monero_rpc(method: str, params: dict = {}) -> dict: response_json = response.json() if 'error' in response_json: - response.status_code = 400 + raise Exception(response_json) response.raise_for_status() @@ -77,10 +77,17 @@ def kraken_request(path: str, payload = {}) -> dict: payload['nonce'] = str(int(1000*time.time())) headers = {} headers['API-Key'] = env.KRAKEN_API_KEY - headers['API-Sign'] = get_kraken_signature(path, payload) - - return requests.post( + headers['API-Sign'] = get_kraken_signature(path, payload) + + response = requests.post( 'https://api.kraken.com' + path, headers=headers, data=payload - ).json()['result'] \ No newline at end of file + ) + + response_json = response.json() + + if 'error' in response_json: + raise Exception(response_json) + + return response_json \ No newline at end of file From 8aadf0ac558ef78e13c8eb1d924e61af6d93c737 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 9 Sep 2024 19:44:17 -0300 Subject: [PATCH 05/18] chore(compose): set rpc url env variables --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3cd822b..f006b27 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,11 +31,11 @@ services: container_name: seed-importer environment: - PYTHONUNBUFFERED=1 - - ELECTRUM_RPC_URL=${ELECTRUM_RPC_URL} + - ELECTRUM_RPC_URL=electrum-client:7000 - ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME} - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} - - MONERO_RPC_URL=${MONERO_RPC_URL} + - MONERO_RPC_URL=http://monero-wallet-rpc:18082/json_rpc - MONERO_RPC_USERNAME=${MONERO_RPC_USERNAME} - MONERO_RPC_PASSWORD=${MONERO_RPC_PASSWORD} - MONERO_WALLET_SEED=${MONERO_WALLET_SEED} @@ -53,11 +53,11 @@ services: restart: unless-stopped environment: - PYTHONUNBUFFERED=1 - - ELECTRUM_RPC_URL=${ELECTRUM_RPC_URL} + - ELECTRUM_RPC_URL=electrum-client:7000 - ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME} - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} - - MONERO_RPC_URL=${MONERO_RPC_URL} + - MONERO_RPC_URL=http://monero-wallet-rpc:18082/json_rpc - MONERO_RPC_USERNAME=${MONERO_RPC_USERNAME} - MONERO_RPC_PASSWORD=${MONERO_RPC_PASSWORD} - MONERO_WALLET_SEED=${MONERO_WALLET_SEED} From 29b85fb9fe638eaa0fa1e2f957738c5ff583f383 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 9 Sep 2024 20:18:34 -0300 Subject: [PATCH 06/18] fix: remove test addresses --- src/autoforward.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/autoforward.py b/src/autoforward.py index f2de1a4..8b76300 100644 --- a/src/autoforward.py +++ b/src/autoforward.py @@ -93,8 +93,7 @@ def attempt_bitcoin_autoforward(): fee_rate = get_bitcoin_fee_rate() set_bitcoin_fee_rate(fee_rate) - # REMOVE THIS ADDRESS - address = 'bc1qwqp0fczemr7044vas6a36nn57ecvs7a7cmferj' or get_new_kraken_address('XBT') + address = get_new_kraken_address('XBT') try: psbt = create_psbt(address) @@ -127,7 +126,7 @@ def attempt_monero_autoforward(): print(util.get_time(), 'No enough monero balance to autoforward.') return - address = '88PJJSQ4NYNZ4kFNu516GMgkjX1w7B7FwAdh2PwFJakb4JXYUVeJqt21WSCiccqvSLA2NwRkSMeiXfgpcrmmwr4rQM9Yubq' or 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}!') From d52371d059244443028a895bbb9512d83361f94d Mon Sep 17 00:00:00 2001 From: Artur Date: Thu, 12 Sep 2024 14:03:04 -0300 Subject: [PATCH 07/18] chore: use poetry for package management --- Dockerfile | 32 +- docker-compose.yml | 6 +- electrum-client/Dockerfile | 8 +- poetry.lock | 604 +++++++++++++++++++++++++++++++++++++ pyproject.toml | 16 + requirements.txt | 2 - 6 files changed, 650 insertions(+), 18 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile index f1896f0..8654357 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,27 @@ -FROM python:3.12-alpine +FROM python:3.12-alpine AS builder -RUN apk add --no-cache build-base musl-dev gcc -ENV VIRTUAL_ENV=/opt/venv -RUN python3 -m venv $VIRTUAL_ENV -ENV PATH="$VIRTUAL_ENV/bin:$PATH" +RUN pip install poetry==1.8.3 --user +RUN apk add build-base musl-dev gcc + +ENV POETRY_NO_INTERACTION=1 \ + POETRY_VIRTUALENVS_IN_PROJECT=1 \ + POETRY_VIRTUALENVS_CREATE=1 \ + POETRY_CACHE_DIR=/tmp/poetry_cache \ + PATH=/root/.local/bin/:$PATH + +WORKDIR /app + +COPY pyproject.toml poetry.lock ./ + +RUN --mount=type=cache,target=$POETRY_CACHE_DIR poetry install --no-root + +FROM python:3.12-alpine AS runtime WORKDIR /app -# Install dependencies: -COPY requirements.txt . -RUN pip install -r requirements.txt -COPY src src +ENV VIRTUAL_ENV=/app/.venv \ + PATH="/app/.venv/bin:$PATH" + +COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} + +COPY src src \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index f006b27..bfaa453 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: - MONERO_WALLET_SEED=${MONERO_WALLET_SEED} - MONERO_WALLET_PASSWORD=${MONERO_WALLET_PASSWORD} - MONERO_WALLET_HEIGHT=${MONERO_WALLET_HEIGHT} - command: python /app/src/seed-importer.py + command: python ./src/seed-importer.py depends_on: - electrum-client - monero-wallet-rpc @@ -65,7 +65,7 @@ services: - MONERO_WALLET_HEIGHT=${MONERO_WALLET_HEIGHT} - KRAKEN_API_KEY=${KRAKEN_API_KEY} - KRAKEN_API_SECRET=${KRAKEN_API_SECRET} - command: python /app/src/autoforward.py + command: python ./src/autoforward.py depends_on: - seed-importer @@ -78,7 +78,7 @@ services: - PYTHONUNBUFFERED=1 - KRAKEN_API_KEY=${KRAKEN_API_KEY} - KRAKEN_API_SECRET=${KRAKEN_API_SECRET} - command: python /app/src/autoconvert.py + command: python ./src/autoconvert.py depends_on: - seed-importer diff --git a/electrum-client/Dockerfile b/electrum-client/Dockerfile index 15b5f91..8a4e3f0 100644 --- a/electrum-client/Dockerfile +++ b/electrum-client/Dockerfile @@ -3,10 +3,10 @@ FROM python:3.12-alpine ARG VERSION ARG CHECKSUM_SHA512 -ENV ELECTRUM_VERSION $VERSION -ENV ELECTRUM_USER electrum -ENV ELECTRUM_HOME /home/$ELECTRUM_USER -ENV ELECTRUM_NETWORK mainnet +ENV ELECTRUM_VERSION=$VERSION +ENV ELECTRUM_USER=electrum +ENV ELECTRUM_HOME=/home/$ELECTRUM_USER +ENV ELECTRUM_NETWORK=mainnet RUN adduser -D $ELECTRUM_USER diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..d853a8d --- /dev/null +++ b/poetry.lock @@ -0,0 +1,604 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "asn1crypto" +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 = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] + +[[package]] +name = "bip-utils" +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" +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"}, +] + +[package.dependencies] +cbor2 = ">=5.1.2,<6.0.0" +coincurve = {version = ">=19.0.1", markers = "python_version >= \"3.12\""} +crcmod = ">=1.7,<2.0" +ecdsa = ">=0.17,<1.0" +ed25519-blake2b = {version = ">=1.4.1,<2.0.0", markers = "python_version >= \"3.12\""} +py-sr25519-bindings = {version = ">=0.2.0,<2.0.0", markers = "python_version >= \"3.11\""} +pycryptodome = ">=3.15,<4.0" +pynacl = ">=1.5,<2.0" + +[package.extras] +develop = ["coverage (>=5.3)", "flake8 (>=3.8)", "isort (>=5.8)", "mypy (>=0.900)", "prospector[with-mypy,with-pyroma] (>=1.7)", "pytest (>=7.0)", "pytest-cov (>=2.10)"] + +[[package]] +name = "cbor2" +version = "5.6.4" +description = "CBOR (de)serializer with extensive tag support" +optional = false +python-versions = ">=3.8" +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"}, + {file = "cbor2-5.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de7137622204168c3a57882f15dd09b5135bda2bcb1cf8b56b58d26b5150dfca"}, + {file = "cbor2-5.6.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3545e1e62ec48944b81da2c0e0a736ca98b9e4653c2365cae2f10ae871e9113"}, + {file = "cbor2-5.6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6749913cd00a24eba17406a0bfc872044036c30a37eb2fcde7acfd975317e8a"}, + {file = "cbor2-5.6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:57db966ab08443ee54b6f154f72021a41bfecd4ba897fe108728183ad8784a2a"}, + {file = "cbor2-5.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:380e0c7f4db574dcd86e6eee1b0041863b0aae7efd449d49b0b784cf9a481b9b"}, + {file = "cbor2-5.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5c763d50a1714e0356b90ad39194fc8ef319356b89fb001667a2e836bfde88e3"}, + {file = "cbor2-5.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58a7ac8861857a9f9b0de320a4808a2a5f68a2599b4c14863e2748d5a4686c99"}, + {file = "cbor2-5.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d715b2f101730335e84a25fe0893e2b6adf049d6d44da123bf243b8c875ffd8"}, + {file = "cbor2-5.6.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f53a67600038cb9668720b309fdfafa8c16d1a02570b96d2144d58d66774318"}, + {file = "cbor2-5.6.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f898bab20c4f42dca3688c673ff97c2f719b1811090430173c94452603fbcf13"}, + {file = "cbor2-5.6.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e5d50fb9f47d295c1b7f55592111350424283aff4cc88766c656aad0300f11f"}, + {file = "cbor2-5.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:7f9d867dcd814ab8383ad132eb4063e2b69f6a9f688797b7a8ca34a4eadb3944"}, + {file = "cbor2-5.6.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e0860ca88edf8aaec5461ce0e498eb5318f1bcc70d93f90091b7a1f1d351a167"}, + {file = "cbor2-5.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c38a0ed495a63a8bef6400158746a9cb03c36f89aeed699be7ffebf82720bf86"}, + {file = "cbor2-5.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c8d8c2f208c223a61bed48dfd0661694b891e423094ed30bac2ed75032142aa"}, + {file = "cbor2-5.6.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cd2ce6136e1985da989e5ba572521023a320dcefad5d1fff57fba261de80ca"}, + {file = "cbor2-5.6.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7facce04aed2bf69ef43bdffb725446fe243594c2451921e89cc305bede16f02"}, + {file = "cbor2-5.6.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f9c8ee0d89411e5e039a4f3419befe8b43c0dd8746eedc979e73f4c06fe0ef97"}, + {file = "cbor2-5.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:9b45d554daa540e2f29f1747df9f08f8d98ade65a67b1911791bc193d33a5923"}, + {file = "cbor2-5.6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a5cb2c16687ccd76b38cfbfdb34468ab7d5635fb92c9dc5e07831c1816bd0a9"}, + {file = "cbor2-5.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f985f531f7495527153c4f66c8c143e4cf8a658ec9e87b14bc5438e0a8d0911"}, + {file = "cbor2-5.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d9c7b4bd7c3ea7e5587d4f1bbe073b81719530ddadb999b184074f064896e2"}, + {file = "cbor2-5.6.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d06184dcdc275c389fee3cd0ea80b5e1769763df15f93ecd0bf4c281817365"}, + {file = "cbor2-5.6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e9ba7116f201860fb4c3e80ef36be63851ec7e4a18af70fea22d09cab0b000bf"}, + {file = "cbor2-5.6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:341468ae58bdedaa05c907ab16e90dd0d5c54d7d1e66698dfacdbc16a31e815b"}, + {file = "cbor2-5.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:bcb4994be1afcc81f9167c220645d878b608cae92e19f6706e770f9bc7bbff6c"}, + {file = "cbor2-5.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41c43abffe217dce70ae51c7086530687670a0995dfc90cc35f32f2cf4d86392"}, + {file = "cbor2-5.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:227a7e68ba378fe53741ed892b5b03fe472b5bd23ef26230a71964accebf50a2"}, + {file = "cbor2-5.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13521b7c9a0551fcc812d36afd03fc554fa4e1b193659bb5d4d521889aa81154"}, + {file = "cbor2-5.6.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4816d290535d20c7b7e2663b76da5b0deb4237b90275c202c26343d8852b8a"}, + {file = "cbor2-5.6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1e98d370106821335efcc8fbe4136ea26b4747bf29ca0e66512b6c4f6f5cc59f"}, + {file = "cbor2-5.6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:68743a18e16167ff37654a29321f64f0441801dba68359c82dc48173cc6c87e1"}, + {file = "cbor2-5.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:7ba5e9c6ed17526d266a1116c045c0941f710860c5f2495758df2e0d848c1b6d"}, + {file = "cbor2-5.6.4-py3-none-any.whl", hash = "sha256:fe411c4bf464f5976605103ebcd0f60b893ac3e4c7c8d8bc8f4a0cb456e33c60"}, + {file = "cbor2-5.6.4.tar.gz", hash = "sha256:1c533c50dde86bef1c6950602054a0ffa3c376e8b0e20c7b8f5b108793f6983e"}, +] + +[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"] +test = ["coverage (>=7)", "hypothesis", "pytest"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +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"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +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" +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"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "coincurve" +version = "20.0.0" +description = "Cross-platform Python CFFI bindings for libsecp256k1" +optional = false +python-versions = ">=3.8" +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"}, + {file = "coincurve-20.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22d70dd55d13fd427418eb41c20fde0a20a5e5f016e2b1bb94710701e759e7e0"}, + {file = "coincurve-20.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f18d481eaae72c169f334cde1fd22011a884e0c9c6adc3fdc1fd13df8236a3"}, + {file = "coincurve-20.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de1ec57f43c3526bc462be58fb97910dc1fdd5acab6c71eda9f9719a5bd7489"}, + {file = "coincurve-20.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6f007c44c726b5c0b3724093c0d4fb8e294f6b6869beb02d7473b21777473a3"}, + {file = "coincurve-20.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0ff1f3b81330db5092c24da2102e4fcba5094f14945b3eb40746456ceabdd6d9"}, + {file = "coincurve-20.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82f7de97694d9343f26bd1c8e081b168e5f525894c12445548ce458af227f536"}, + {file = "coincurve-20.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e905b4b084b4f3b61e5a5d58ac2632fd1d07b7b13b4c6d778335a6ca1dafd7a3"}, + {file = "coincurve-20.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:3657bb5ed0baf1cf8cf356e7d44aa90a7902cc3dd4a435c6d4d0bed0553ad4f7"}, + {file = "coincurve-20.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44087d1126d43925bf9a2391ce5601bf30ce0dba4466c239172dc43226696018"}, + {file = "coincurve-20.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccf0ba38b0f307a9b3ce28933f6c71dc12ef3a0985712ca09f48591afd597c8"}, + {file = "coincurve-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566bc5986debdf8572b6be824fd4de03d533c49f3de778e29f69017ae3fe82d8"}, + {file = "coincurve-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4d70283168e146f025005c15406086513d5d35e89a60cf4326025930d45013a"}, + {file = "coincurve-20.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:763c6122dd7d5e7a81c86414ce360dbe9a2d4afa1ca6c853ee03d63820b3d0c5"}, + {file = "coincurve-20.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f00c361c356bcea386d47a191bb8ac60429f4b51c188966a201bfecaf306ff7f"}, + {file = "coincurve-20.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4af57bdadd2e64d117dd0b33cfefe76e90c7a6c496a7b034fc65fd01ec249b15"}, + {file = "coincurve-20.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a26437b7cbde13fb6e09261610b788ca2a0ca2195c62030afd1e1e0d1a62e035"}, + {file = "coincurve-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed51f8bba35e6c7676ad65539c3dbc35acf014fc402101fa24f6b0a15a74ab9e"}, + {file = "coincurve-20.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:594b840fc25d74118407edbbbc754b815f1bba9759dbf4f67f1c2b78396df2d3"}, + {file = "coincurve-20.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4df4416a6c0370d777aa725a25b14b04e45aa228da1251c258ff91444643f688"}, + {file = "coincurve-20.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1ccc3e4db55abf3fc0e604a187fdb05f0702bc5952e503d9a75f4ae6eeb4cb3a"}, + {file = "coincurve-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8335b1658a2ef5b3eb66d52647742fe8c6f413ad5b9d5310d7ea6d8060d40f"}, + {file = "coincurve-20.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ac025e485a0229fd5394e0bf6b4a75f8a4f6cee0dcf6f0b01a2ef05c5210ff"}, + {file = "coincurve-20.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e46e3f1c21b3330857bcb1a3a5b942f645c8bce912a8a2b252216f34acfe4195"}, + {file = "coincurve-20.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:df9ff9b17a1d27271bf476cf3fa92df4c151663b11a55d8cea838b8f88d83624"}, + {file = "coincurve-20.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4155759f071375699282e03b3d95fb473ee05c022641c077533e0d906311e57a"}, + {file = "coincurve-20.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0530b9dd02fc6f6c2916716974b79bdab874227f560c422801ade290e3fc5013"}, + {file = "coincurve-20.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:eacf9c0ce8739c84549a89c083b1f3526c8780b84517ee75d6b43d276e55f8a0"}, + {file = "coincurve-20.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:52a67bfddbd6224dfa42085c88ad176559801b57d6a8bd30d92ee040de88b7b3"}, + {file = "coincurve-20.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e951b1d695b62376f60519a84c4facaf756eeb9c5aff975bea0942833f185d"}, + {file = "coincurve-20.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e9e548db77f4ea34c0d748dddefc698adb0ee3fab23ed19f80fb2118dac70f6"}, + {file = "coincurve-20.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdbf0da0e0809366fdfff236b7eb6e663669c7b1f46361a4c4d05f5b7e94c57"}, + {file = "coincurve-20.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72222b4ecd3952e8ffcbf59bc7e0d1b181161ba170b60e5c8e1f359a43bbe7e"}, + {file = "coincurve-20.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9add43c4807f0c17a940ce4076334c28f51d09c145cd478400e89dcfb83fb59d"}, + {file = "coincurve-20.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc94cceea6ec8863815134083e6221a034b1ecef822d0277cf6ad2e70009b7f"}, + {file = "coincurve-20.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ffbdfef6a6d147988eabaed681287a9a7e6ba45ecc0a8b94ba62ad0a7656d97"}, + {file = "coincurve-20.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13335c19c7e5f36eaba2a53c68073d981980d7dc7abfee68d29f2da887ccd24e"}, + {file = "coincurve-20.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:7fbfb8d16cf2bea2cf48fc5246d4cb0a06607d73bb5c57c007c9aed7509f855e"}, + {file = "coincurve-20.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4870047704cddaae7f0266a549c927407c2ba0ec92d689e3d2b511736812a905"}, + {file = "coincurve-20.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81ce41263517b0a9f43cd570c87720b3c13324929584fa28d2e4095969b6015d"}, + {file = "coincurve-20.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:572083ccce6c7b514d482f25f394368f4ae888f478bd0b067519d33160ea2fcc"}, + {file = "coincurve-20.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee5bc78a31a2f1370baf28aaff3949bc48f940a12b0359d1cd2c4115742874e6"}, + {file = "coincurve-20.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2895d032e281c4e747947aae4bcfeef7c57eabfd9be22886c0ca4e1365c7c1f"}, + {file = "coincurve-20.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d3e2f21957ada0e1742edbde117bb41758fa8691b69c8d186c23e9e522ea71cd"}, + {file = "coincurve-20.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c2baa26b1aad1947ca07b3aa9e6a98940c5141c6bdd0f9b44d89e36da7282ffa"}, + {file = "coincurve-20.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7eacc7944ddf9e2b7448ecbe84753841ab9874b8c332a4f5cc3b2f184db9f4a2"}, + {file = "coincurve-20.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:c293c095dc690178b822cadaaeb81de3cc0d28f8bdf8216ed23551dcce153a26"}, + {file = "coincurve-20.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:11a47083a0b7092d3eb50929f74ffd947c4a5e7035796b81310ea85289088c7a"}, + {file = "coincurve-20.0.0.tar.gz", hash = "sha256:872419e404300302e938849b6b92a196fabdad651060b559dc310e52f8392829"}, +] + +[package.dependencies] +asn1crypto = "*" +cffi = ">=1.3.0" + +[package.extras] +dev = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "crcmod" +version = "1.7" +description = "CRC Generator" +optional = false +python-versions = "*" +files = [ + {file = "crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"}, +] + +[[package]] +name = "ecdsa" +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" +files = [ + {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, + {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "ed25519-blake2b" +version = "1.4.1" +description = "Ed25519 public-key signatures (BLAKE2b fork)" +optional = false +python-versions = "*" +files = [ + {file = "ed25519-blake2b-1.4.1.tar.gz", hash = "sha256:731e9f93cd1ac1a64649575f3519a99ffe0bb1e4cf7bf5f5f0be513a39df7363"}, +] + +[[package]] +name = "idna" +version = "3.8" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, +] + +[[package]] +name = "py-sr25519-bindings" +version = "0.2.0" +description = "Python bindings for sr25519 library" +optional = false +python-versions = "*" +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"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f12122a18b688e4a4bf0e74d3969d9e3f6f83d2b01fe88ab5f19c969e95192a2"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2815ecc958f6edbad79fee76899bd33b8950caa7106c1db08c828ec90e16fa7"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfe52e73d7f0237820f7a935397d5004733a1d890464701f2c3c71be6033c442"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:df7e2fad636831919bfa479cd4b6fffdd429cde778da72b1834c1434dadaf982"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f4ebeb2aac26a39160f2fad8ffc40ff98da835af57618c0446637bf182b9c927"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:942a6b52e871d6e152dda80a60ed338dccedc69b6375e080e496bf886f2556c0"}, + {file = "py_sr25519_bindings-0.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b24307c34a06209d0e34ca15ab4c0275617538dfdac1eac8aa25e792fa9f4108"}, + {file = "py_sr25519_bindings-0.2.0-cp310-none-win32.whl", hash = "sha256:2e06a2d1119a2ad063f11448bb27ec4f4ba77416043d98ae28ef30624cf0e12d"}, + {file = "py_sr25519_bindings-0.2.0-cp310-none-win_amd64.whl", hash = "sha256:16b36d9fe8bda873ab8376f3a4d0894b8d4ab2d702665afc3ab3ca69f0dc9495"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:54e8c41081a4c23eca4b19f52de2514c48ddec6f49844dff7ad4cfac0bc11712"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c73bd1a87849db9cd0e664b2d2e14208183dd8d11ac083d70e688fc28283a71"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47d21382ea24f7f25e72cdddaca2f013ce46cc7983bcfebc611c795cea177eff"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1448cf55bbf6f52d2e24766a8a84ba6d77100a991897e8519711ccd7409830"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:392b8b9875c89c9302930ad3d59567b62176f33adeee96a55ff61ba17fb7aac2"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7b56b5cbbfb36b41ddfa462989a03386590ac036f3a755ef64fffeb2fed88654"}, + {file = "py_sr25519_bindings-0.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8f06ea3237e06666e3a4ff4719b4fba415472943831b229428753c37d5ecd1b4"}, + {file = "py_sr25519_bindings-0.2.0-cp311-none-win_amd64.whl", hash = "sha256:d62af30b2022f5fa787e46c06823c35a21abe791bf55012f498f9ba8e4baabc8"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ceafa0c31b49f2128461eb2c6ea18dc5d0bfae7218a100be7153f271e46bac49"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c8dedb8525556591738a64310875df70ea67886e5a40f2799bd96ef8848936cf"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ce149796923696f5cfc6263f135674a14fe2d513fd35b2bfa73226b940aff648"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d71ca3ba22f98f4c208d509f735fe4eb5aa9e3547a507733a95828adde6cab"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8e20ee0856e8a60682566df955b81e7631670136607da627ab6892df34790d"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0efd5487e3f6d6053cfc4a891b10f729d69263897270d0354f409ee2106fc9b7"}, + {file = "py_sr25519_bindings-0.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ae7f2164d510458740145d20912d5d7a5c45e8fcde7cebd4057f60811ecc276f"}, + {file = "py_sr25519_bindings-0.2.0-cp312-none-win32.whl", hash = "sha256:92382456c6f176c07e0d554c71d483853387885ce17714f8a4b50fdcf7552297"}, + {file = "py_sr25519_bindings-0.2.0-cp312-none-win_amd64.whl", hash = "sha256:48ee4e14a77f815f3996beecb7d7abf422b756e9163ee4df739c1aded8a3e8ba"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:c3de899a1e911b8945f09e6389f8d2df68924c12c78e3e66fedb15f1e4ff56ad"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:758761b605f90e4238304df7520155a3358b13cc55ee18c5113632da17343163"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f63580a224607e68b861eb03421465091c3104b6309e5fca7448f5aa6dbda60"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b999075b76cae8e84d5f19f2c8f28d3f24c93ba858ad49e58bcf22afe0406b"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_28_armv7l.whl", hash = "sha256:5102c94e97d316009ad4482f24d9a933fc0b7eb0bb88e6a784a820cd1bd25827"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b477b18940f472d4e25e141f19503a6e55aadff31b4822228a491c9638096baf"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:7e69bf7bdc9920013c1a2bea25a8b02df9588d9856cb20270f4d8d95b8e83f52"}, + {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:dc436a34e17037833c3909062722ee3d46e28288972c87f619d163d00054d68e"}, + {file = "py_sr25519_bindings-0.2.0-cp36-none-win32.whl", hash = "sha256:fc27c847dd4df727388aaadc3870aeb472f2d5c35717536d319792fe08f6120a"}, + {file = "py_sr25519_bindings-0.2.0-cp36-none-win_amd64.whl", hash = "sha256:0441381c2a6f532831d560a1f2ae8a917c7190cf27f5428d9b0528fa28a72e2d"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:e1471134450e6189b7e38d245ab16b06f3de900b6d07aa66b1e6973cdbc00d01"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:302bd20e75d900d98e7961934b03958e8acc8784eed594ab48f9bb298396c734"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e09ac91f4b2e2b9c50e268f6ee292d9fa447c5b7cc6327cfeae7750d716f49e"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_28_armv7l.whl", hash = "sha256:28b904739147c4f797627bd3b44d1e64d061533253abd1882c6d3b8944e7bbd8"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0931ac85331aae33bef67460a3cce554ef5c1f7dfec0ebe2f5b9ea57c5bee65c"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd8da64f9e42ff973b394ed9164f1e9a454279a058eed08ac8d006fcbd61093b"}, + {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:297ad50e3cace5c89dbf5bd916b714aac3ebe6bc76f85382dac228cbeb71449e"}, + {file = "py_sr25519_bindings-0.2.0-cp37-none-win32.whl", hash = "sha256:422d62ca74ebe5065eca88607552b9a5f1dc4abff0c597cc3793dd8adfb8c4ea"}, + {file = "py_sr25519_bindings-0.2.0-cp37-none-win_amd64.whl", hash = "sha256:d1b0ed9a4dded60f671f34fdd81c974dad159e98f43bcab21833f984e05920f9"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:37f11ffee535c624bf5ddc6109c2cdca9a2dbd10f7d310bcd1dd97f6121c532f"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4e1b553a6b1cc1b0aa9da2d7157329713cc7f299acb12a052d326f9b594b145c"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0b6dcf1328027dba1f9236bd3432cc3cce1de55a12c1a3a4ea7a8dc3ab3e857"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:458c7e6d7447bd267a6f870a8801e995d710952566a0a52634f408bf804cf27a"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d64253d7d08fd6073e7b79bba9cff78687e76698cc210d3c6f236b90766b9421"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_28_armv7l.whl", hash = "sha256:a9aac20a196416b8daf764704a9cee71ddee16bc705d12b5c6bcb6f51e81ac6e"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e162687189cf765f602178aa195a2be4284107622141ff746e92e14e266cf3b7"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d44ab4d150c9bdd1641ccad49942ecf2d0ef61bd66a7da41094bb4a9cbaca529"}, + {file = "py_sr25519_bindings-0.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:251ff9cef5dafd96ff241b77ff471912c40249b6df31e71c6c32de6a26a8dbc6"}, + {file = "py_sr25519_bindings-0.2.0-cp38-none-win32.whl", hash = "sha256:ca9794f9d4fc37cdc8cbb6724d5432a064d22c26ecde312928154b6bc691f4d3"}, + {file = "py_sr25519_bindings-0.2.0-cp38-none-win_amd64.whl", hash = "sha256:6406cb0aeb5cbb8cfaa37d59d15d7640c0d812a1cbb55657bee52fd3d9e92aa9"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:b9da73447c8f5b8392a8229c2b65d742709c6aa2d0c6b32e39b635fb245145f1"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:7f00236a802d6d3f3705713d5352ba968c0ce353a20519c445e66ce19869bfdc"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d473199c0dbad846b0723c6663b1b6a04040ccdca700cb1609acac3e621f2087"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44bede0dd42f75cf849d3ccb4e443d6425218035bc00a6330b11dc2cc1146f3b"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_28_armv7l.whl", hash = "sha256:a8e462d2442726d9db07854dc2eb640b1a8a548948b1ff3aa580771ab739bab8"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:55b1f67fdaeab91481fda54432dffdf87ed516d26461d31e70911c7ea55d6164"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec11493d59075ba75fe0bc0312d502ffdc45b641a46fb084bf8b04906597688b"}, + {file = "py_sr25519_bindings-0.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:101ee46368da149ad332aea225d4ff2907dffce574e8f8f7fe56f5c29211f333"}, + {file = "py_sr25519_bindings-0.2.0-cp39-none-win32.whl", hash = "sha256:909f13f63f67f1e5595d4d495cf8a3c95e392626c08f94550cbf8f0e8ea1c743"}, + {file = "py_sr25519_bindings-0.2.0-cp39-none-win_amd64.whl", hash = "sha256:b3f86e4aad6c2b8ff74af76f38fde7fbaf9dd83bc4a7c259709092008c3b8e5d"}, + {file = "py_sr25519_bindings-0.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38db0ee90bd676b9df7ddd03fcb2113b5a5e9d9c984d82426728acc0e9d54277"}, + {file = "py_sr25519_bindings-0.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dfe767069d5c5e8a313e77b6bd681ea4f6b5988b09b6b4c9399e255fe4a7c53"}, + {file = "py_sr25519_bindings-0.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8951d1a6e310a682a6253d547e44a9e7a606476dbc18dea3f121d98bdb81042"}, + {file = "py_sr25519_bindings-0.2.0.tar.gz", hash = "sha256:0c2fe92b7cdcebf6c5611a90054f8ba6ea90b68b8832896d2dc565537bc40b0c"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pycryptodome" +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.*" +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"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, +] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +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"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +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)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "312bfc0d67e89feee1abbdbabc31649ff525b844d43eeaa06d57b9e9d26fbf06" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b77f615 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "magic-autoforward-autoconvert" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" + +[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" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a7be810..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests -bip_utils \ No newline at end of file From 7152fc985effdfef55e9aa093621beb908ccd2ab Mon Sep 17 00:00:00 2001 From: Seth For Privacy <40500387+sethforprivacy@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:05:30 +0000 Subject: [PATCH 08/18] Correct monero-wallet-rpc image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index bfaa453..d7d47b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: - ELECTRUM_PASSWORD=${ELECTRUM_RPC_PASSWORD} monero-wallet-rpc: - image: sethsimmons/simple-monero-wallet-rpc:latest + image: ghcr.io/sethforprivacy/simple-monero-wallet-rpc:latest restart: unless-stopped container_name: monero-wallet-rpc volumes: From 923ad26be999dac013e1b646f4601038f3cd6e6e Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer <12520755+SamsungGalaxyPlayer@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:18:58 -0500 Subject: [PATCH 09/18] MAX_SLIPPAGE_PERCENT to constants --- src/autoconvert.py | 3 +-- src/constants.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/autoconvert.py b/src/autoconvert.py index 2b6a312..12cedb1 100644 --- a/src/autoconvert.py +++ b/src/autoconvert.py @@ -3,10 +3,9 @@ import random import time +from constants import MAX_SLIPPAGE_PERCENT import util -MAX_SLIPPAGE_PERCENT = 1 - order_min = { 'XBT': 0.0001, 'XMR': 0.03 diff --git a/src/constants.py b/src/constants.py index 09bb41d..8ea48d9 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,3 +1,4 @@ MIN_BITCOIN_SEND_AMOUNT = 0.0001 MAX_BITCOIN_FEE_PERCENT = 10 -MIN_MONERO_SEND_AMOUNT = 0.01 \ No newline at end of file +MIN_MONERO_SEND_AMOUNT = 0.01 +MAX_SLIPPAGE_PERCENT = 1 \ No newline at end of file From 1da07edc14e33601c167ebaa0294218f724e2f70 Mon Sep 17 00:00:00 2001 From: Artur Date: Thu, 3 Oct 2024 15:29:47 -0300 Subject: [PATCH 10/18] chore: support setting an electrum server address for electrum client --- .env.example | 1 + docker-compose.yml | 1 + electrum-client/docker-entrypoint.sh | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index d613d58..24bfe2c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ ELECTRUM_RPC_URL="http://electrum-client:7000" ELECTRUM_RPC_USERNAME="user" ELECTRUM_RPC_PASSWORD="" +ELECTRUM_SERVER_ADDRESS="" BITCOIN_WALLET_SEED="" MONERO_DAEMON_ADDRESS="xmr.tcpcat.net:18089" diff --git a/docker-compose.yml b/docker-compose.yml index bfaa453..4e10d6f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: environment: - ELECTRUM_USER=${ELECTRUM_RPC_USERNAME} - ELECTRUM_PASSWORD=${ELECTRUM_RPC_PASSWORD} + - ELECTRUM_SERVER_ADDRESS=${ELECTRUM_SERVER_ADDRESS} monero-wallet-rpc: image: sethsimmons/simple-monero-wallet-rpc:latest diff --git a/electrum-client/docker-entrypoint.sh b/electrum-client/docker-entrypoint.sh index cbe3e67..75dd531 100755 --- a/electrum-client/docker-entrypoint.sh +++ b/electrum-client/docker-entrypoint.sh @@ -8,4 +8,10 @@ electrum --offline setconfig rpcuser ${ELECTRUM_USER} electrum --offline setconfig rpcpassword ${ELECTRUM_PASSWORD} electrum --offline setconfig rpchost 0.0.0.0 electrum --offline setconfig rpcport 7000 -electrum daemon "$@" \ No newline at end of file + +if [ -n "${ELECTRUM_SERVER_ADDRESS}" ]; then + electrum daemon -1 -s "${ELECTRUM_SERVER_ADDRESS}" "$@" +else + electrum daemon "$@" +fi + From b9a0eb64a8dd7a7563d80a8eba03f46b6db8d657 Mon Sep 17 00:00:00 2001 From: Seth For Privacy <40500387+sethforprivacy@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:18:57 +0000 Subject: [PATCH 11/18] Fix JSON response error handling --- src/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.py b/src/util.py index 954f300..1b98073 100644 --- a/src/util.py +++ b/src/util.py @@ -87,7 +87,7 @@ def kraken_request(path: str, payload = {}) -> dict: response_json = response.json() - if 'error' in response_json: + if response_json["error"]: raise Exception(response_json) - return response_json \ No newline at end of file + return response_json From 8f8982855af45b0bc38d350ba2e22c7588da6845 Mon Sep 17 00:00:00 2001 From: Seth For Privacy <40500387+sethforprivacy@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:19:37 +0000 Subject: [PATCH 12/18] Fix typo in log copy --- src/autoconvert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autoconvert.py b/src/autoconvert.py index 12cedb1..a022fb1 100644 --- a/src/autoconvert.py +++ b/src/autoconvert.py @@ -28,7 +28,7 @@ def attempt_sell(asset: Literal['XBT', 'XMR']): to_sell_amount = 0 if balance < order_min[asset]: - print(util.get_time(), f'No enough {asset} balance to sell.') + print(util.get_time(), f'Not enough {asset} balance to sell.') return bids = get_bids(asset) @@ -73,4 +73,4 @@ def attempt_sell(asset: Literal['XBT', 'XMR']): print(traceback.format_exc()) delay = random.randint(30, 90) - time.sleep(delay) \ No newline at end of file + time.sleep(delay) From 90233aba960766c1eda651226636d74f2a5601d6 Mon Sep 17 00:00:00 2001 From: Seth For Privacy <40500387+sethforprivacy@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:53:15 +0000 Subject: [PATCH 13/18] Fix electrum client connections Missing the protocol (http://) broke all Electrum connections previously. --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 41e9fcb..99c4f03 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: container_name: seed-importer environment: - PYTHONUNBUFFERED=1 - - ELECTRUM_RPC_URL=electrum-client:7000 + - ELECTRUM_RPC_URL=http://electrum-client:7000 - ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME} - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} @@ -54,7 +54,7 @@ services: restart: unless-stopped environment: - PYTHONUNBUFFERED=1 - - ELECTRUM_RPC_URL=electrum-client:7000 + - ELECTRUM_RPC_URL=http://electrum-client:7000 - ELECTRUM_RPC_USERNAME=${ELECTRUM_RPC_USERNAME} - ELECTRUM_RPC_PASSWORD=${ELECTRUM_RPC_PASSWORD} - BITCOIN_WALLET_SEED=${BITCOIN_WALLET_SEED} From afbfb362e7e7dc7af4e871e1d9afe139650ffeec Mon Sep 17 00:00:00 2001 From: Artur Date: Fri, 4 Oct 2024 11:54:16 -0300 Subject: [PATCH 14/18] feat: accept MAX_BITCOIN_FEE_PERCENT and MAX_SLIPPAGE_PERCENT as env variables --- .env.example | 13 +++++++------ src/autoconvert.py | 4 ++-- src/autoforward.py | 4 ++-- src/constants.py | 2 -- src/env.py | 3 +++ 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 24bfe2c..f787fd4 100644 --- a/.env.example +++ b/.env.example @@ -1,16 +1,17 @@ ELECTRUM_RPC_URL="http://electrum-client:7000" +MONERO_RPC_URL="http://monero-wallet-rpc:18082/json_rpc" ELECTRUM_RPC_USERNAME="user" +MONERO_RPC_USERNAME="user" +MONERO_DAEMON_ADDRESS="xmr.tcpcat.net:18089" + ELECTRUM_RPC_PASSWORD="" ELECTRUM_SERVER_ADDRESS="" -BITCOIN_WALLET_SEED="" - -MONERO_DAEMON_ADDRESS="xmr.tcpcat.net:18089" -MONERO_RPC_URL="http://monero-wallet-rpc:18082/json_rpc" -MONERO_RPC_USERNAME="user" MONERO_RPC_PASSWORD="" +BITCOIN_WALLET_SEED="" MONERO_WALLET_SEED="" MONERO_WALLET_PASSWORD="" MONERO_WALLET_HEIGHT="" - KRAKEN_API_KEY="" KRAKEN_API_SECRET="" +MAX_BITCOIN_FEE_PERCENT="10" +MAX_SLIPPAGE_PERCENT="1" \ No newline at end of file diff --git a/src/autoconvert.py b/src/autoconvert.py index a022fb1..9a37df3 100644 --- a/src/autoconvert.py +++ b/src/autoconvert.py @@ -3,8 +3,8 @@ import random import time -from constants import MAX_SLIPPAGE_PERCENT import util +import env order_min = { 'XBT': 0.0001, @@ -40,7 +40,7 @@ def attempt_sell(asset: Literal['XBT', 'XMR']): slippage_percent = ((market_price - bid_price) / market_price) * 100 # Example ((161.06-161.05)/161.06)*100 = 0.0062088662610207 # Break loop if slippage is too high - if slippage_percent > MAX_SLIPPAGE_PERCENT: + if slippage_percent > env.MAX_SLIPPAGE_PERCENT: break # Otherwise, add the bid_volume to the to_sell_amount diff --git a/src/autoforward.py b/src/autoforward.py index 8b76300..e7eb9aa 100644 --- a/src/autoforward.py +++ b/src/autoforward.py @@ -4,7 +4,7 @@ import requests import json -from constants import MAX_BITCOIN_FEE_PERCENT, MIN_BITCOIN_SEND_AMOUNT, MIN_MONERO_SEND_AMOUNT +from constants import MIN_BITCOIN_SEND_AMOUNT, MIN_MONERO_SEND_AMOUNT import util import env @@ -110,7 +110,7 @@ def attempt_bitcoin_autoforward(): total_fee = get_total_psbt_fee(psbt_data) amount = balance - if total_fee / amount * 100 > MAX_BITCOIN_FEE_PERCENT: + if total_fee / amount * 100 > env.MAX_BITCOIN_FEE_PERCENT: print(util.get_time(), f'Not autoforwarding due to high transaction fee.') return diff --git a/src/constants.py b/src/constants.py index 8ea48d9..3799037 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,4 +1,2 @@ MIN_BITCOIN_SEND_AMOUNT = 0.0001 -MAX_BITCOIN_FEE_PERCENT = 10 MIN_MONERO_SEND_AMOUNT = 0.01 -MAX_SLIPPAGE_PERCENT = 1 \ No newline at end of file diff --git a/src/env.py b/src/env.py index fa436c7..80ecfab 100644 --- a/src/env.py +++ b/src/env.py @@ -14,3 +14,6 @@ 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')) \ No newline at end of file From 3b808755e0ba78bfdf24aab81e80da4dc3c02cbe Mon Sep 17 00:00:00 2001 From: Artur Date: Fri, 4 Oct 2024 11:55:00 -0300 Subject: [PATCH 15/18] docs: update README --- README.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 67b04dd..7200f2c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,47 @@ # autoforward-autoconvert -Programs to auto-forward BTC and XMR wallets to Kraken, and then auto-convert to USD + +Programs to auto-forward BTC and XMR wallets to Kraken, and then auto-convert to USD. + +# Usage + +## Requirements + +- Docker +- Docker Compose + +## Configuration + +Create a `.env` file as a copy of `.env.example` and set the values for the empty variables. + +### Environment variables + +| Variable name | Required | Default | Description | +| - | - | - | - | +| `BITCOIN_WALLET_SEED` | Yes | - | Your BIP39 mnemonic seed. | +| `MONERO_WALLET_SEED` | Yes | - | Your 14/16/24 word 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. | +| `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. | +| `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. | + +## Running + +After setting the required environment variable values, you can run the containers: + +```bash +$ docker-compose --env-file .env up -d +``` + +# Contributing + +Pull requests welcome! +Thanks for supporting MAGIC Grants. + +# License + +[MIT](LICENSE) From 7d5c8ad4428d0275acc99be0a745f446045e112e Mon Sep 17 00:00:00 2001 From: Artur Date: Fri, 4 Oct 2024 17:07:08 -0300 Subject: [PATCH 16/18] feat: fixes and set gap limit --- src/autoforward.py | 21 ++++++++------------- src/seed-importer.py | 8 ++++++-- src/util.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/autoforward.py b/src/autoforward.py index e7eb9aa..b8c11af 100644 --- a/src/autoforward.py +++ b/src/autoforward.py @@ -11,9 +11,6 @@ def get_bitcoin_fee_rate() -> int: return requests.get('https://mempool.space/api/v1/fees/recommended').json()['halfHourFee'] -def open_bitcoin_wallet(): - util.request_electrum_rpc('load_wallet') - 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]) @@ -53,10 +50,6 @@ def sign_psbt(psbt: str) -> str: def broadcast_bitcoin_tx(signed_tx: str): util.request_electrum_rpc('broadcast', [signed_tx]) -def open_monero_wallet() -> None: - params = {'filename': 'wallet', 'password': env.MONERO_WALLET_PASSWORD} - util.request_monero_rpc('open_wallet', params) - def get_monero_balance() -> float: params = {'account_index': 0} return util.request_monero_rpc('get_balance', params)['balance'] / 1000000000000 @@ -84,11 +77,10 @@ def get_new_kraken_address(asset: Literal['XBT', 'XMR']) -> str: raise Exception(f'Kraken did not return a new address: {json.dumps(result, indent=2)}') def attempt_bitcoin_autoforward(): - open_bitcoin_wallet() balance = get_bitcoin_balance() if balance < MIN_BITCOIN_SEND_AMOUNT: - print(util.get_time(), 'No enough bitcoin balance to autoforward.') + print(util.get_time(), 'Not enough bitcoin balance to autoforward.') return fee_rate = get_bitcoin_fee_rate() @@ -115,7 +107,7 @@ def attempt_bitcoin_autoforward(): return signed_tx = sign_psbt(psbt) - # broadcast_bitcoin_tx(signed_tx) + broadcast_bitcoin_tx(signed_tx) print(util.get_time(), f'Autoforwarded {amount} BTC to {address}!') @@ -123,14 +115,17 @@ def attempt_monero_autoforward(): balance = get_monero_balance() if balance < MIN_MONERO_SEND_AMOUNT: - print(util.get_time(), 'No enough monero balance to autoforward.') + print(util.get_time(), 'Not enough monero balance to autoforward.') return address = get_new_kraken_address('XMR') sweep_all_monero(address) print(util.get_time(), f'Autoforwarded {balance} XMR to {address}!') - -while 1: + +util.wait_for_rpc() +util.wait_for_wallets() + +while 1: try: attempt_bitcoin_autoforward() except Exception as e: diff --git a/src/seed-importer.py b/src/seed-importer.py index 9c8df19..407c8f9 100644 --- a/src/seed-importer.py +++ b/src/seed-importer.py @@ -15,7 +15,7 @@ def import_bitcoin_seed(): util.request_electrum_rpc('restore', [zprv]) def import_monero_seed(): - monero_params = { + params = { 'filename': 'wallet', 'seed': env.MONERO_WALLET_SEED, 'password': env.MONERO_RPC_PASSWORD, @@ -24,10 +24,14 @@ def import_monero_seed(): 'autosave_current': True } - util.request_monero_rpc('restore_deterministic_wallet', monero_params) + util.request_monero_rpc('restore_deterministic_wallet', params) + +util.wait_for_rpc() try: import_bitcoin_seed() + util.request_electrum_rpc('load_wallet') + util.request_electrum_rpc('changegaplimit', [1000, 'iknowhatimdoing']) print('Bitcoin seed has successfully been imported!') except Exception as e: print(util.get_time(), 'Error importing bitcoin seed:') diff --git a/src/util.py b/src/util.py index 1b98073..465e3b6 100644 --- a/src/util.py +++ b/src/util.py @@ -65,6 +65,51 @@ def request_monero_rpc(method: str, params: dict = {}): return response_json['result'] +def open_bitcoin_wallet(): + request_electrum_rpc('load_wallet') + +def open_monero_wallet() -> None: + params = {'filename': 'wallet', 'password': env.MONERO_WALLET_PASSWORD} + request_monero_rpc('open_wallet', params) + +def wait_for_rpc(): + print('Waiting for Electrum RPC...') + + while 1: + try: + request_electrum_rpc('getinfo') + break + except: + time.sleep(10) + + print('Waiting for Monero RPC...') + + while 1: + try: + request_electrum_rpc('getinfo') + break + except: + time.sleep(10) + +def wait_for_wallets(): + print('Waiting for Electrum wallet...') + + while 1: + try: + open_bitcoin_wallet() + break + except: + time.sleep(10) + + print('Waiting for Monero wallet...') + + while 1: + try: + open_monero_wallet() + break + except: + time.sleep(10) + def get_kraken_signature(url: str, payload: dict): postdata = urllib.parse.urlencode(payload) encoded = (str(payload['nonce']) + postdata).encode() From 8baff9555559b9d7d772f51eed3855000b28f286 Mon Sep 17 00:00:00 2001 From: Artur Date: Fri, 4 Oct 2024 17:09:00 -0300 Subject: [PATCH 17/18] docs(README): add MONERO_DAEMON_ADDRESS to env variables table and add more instructions --- .env.example | 9 ++------- README.md | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index f787fd4..2a7d7ad 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,6 @@ -ELECTRUM_RPC_URL="http://electrum-client:7000" -MONERO_RPC_URL="http://monero-wallet-rpc:18082/json_rpc" -ELECTRUM_RPC_USERNAME="user" -MONERO_RPC_USERNAME="user" -MONERO_DAEMON_ADDRESS="xmr.tcpcat.net:18089" - -ELECTRUM_RPC_PASSWORD="" ELECTRUM_SERVER_ADDRESS="" +MONERO_DAEMON_ADDRESS="" +ELECTRUM_RPC_PASSWORD="" MONERO_RPC_PASSWORD="" BITCOIN_WALLET_SEED="" MONERO_WALLET_SEED="" diff --git a/README.md b/README.md index 7200f2c..d5ff598 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Programs to auto-forward BTC and XMR wallets to Kraken, and then auto-convert to USD. -# Usage - ## Requirements - Docker @@ -17,18 +15,20 @@ 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 mnemonic seed. | -| `MONERO_WALLET_SEED` | Yes | - | Your 14/16/24 word mnemonic seed. | +| `BITCOIN_WALLET_SEED` | Yes | - | Your BIP39 Bitcoin 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. | | `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. | + ## Running After setting the required environment variable values, you can run the containers: @@ -37,6 +37,20 @@ After setting the required environment variable values, you can run the containe $ docker-compose --env-file .env up -d ``` +## Changing seeds + +If you wish to change any of the wallet seeds in the future, first you have to take down all Docker Compose services and delete the volumes: + +```bash +$ docker-compose down -v +``` + +Change the seeds in the `.env` file and start all services again: + +```bash +$ docker-compose --env-file .env up -d +``` + # Contributing Pull requests welcome! From d69995e960138a6a9fff0566f8b59834d870b178 Mon Sep 17 00:00:00 2001 From: Artur <33733651+Keeqler@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:28:11 -0300 Subject: [PATCH 18/18] fix: correctly return result for kraken request --- src/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.py b/src/util.py index 465e3b6..55c0ddd 100644 --- a/src/util.py +++ b/src/util.py @@ -132,7 +132,7 @@ def kraken_request(path: str, payload = {}) -> dict: response_json = response.json() - if response_json["error"]: + if response_json['error']: raise Exception(response_json) - return response_json + return response_json['result']