From a3c20609c0314808f826daa657b506ab2db12059 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Mon, 29 Sep 2025 13:52:37 -0400 Subject: [PATCH] Fix DOGE overpayment refund - Invalid amount precision error * Fix "Invalid amount" error in DOGE refunds by rounding to 8 decimal places * Add detailed RPC error logging to DogecoinClient for better debugging * Add comment parameter to sendtoaddress calls for transaction labeling * Identified root cause: Dogecoin daemon rejects amounts with >8 decimal precision * Refund amount changed from 1.0385903528 to 1.03859035 DOGE The system was correctly detecting overpayments (5.5 vs 4.358 DOGE expected) but failing on the actual refund transaction due to precision requirements. --- make_post_sell/lib/crypto_clients.py | 27 +++++++ make_post_sell/lib/crypto_payment_rescue.py | 13 ++- monero-sweep-all.sh | 88 +++++++++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100755 monero-sweep-all.sh diff --git a/make_post_sell/lib/crypto_clients.py b/make_post_sell/lib/crypto_clients.py index 9702e30..4272edc 100644 --- a/make_post_sell/lib/crypto_clients.py +++ b/make_post_sell/lib/crypto_clients.py @@ -244,16 +244,37 @@ class DogecoinClient: ) try: + import logging + + logger = logging.getLogger(__name__) + logger.info( + f"DogecoinClient._call: Sending {method} with params {params} to {self.rpc_url}" + ) + logger.info( + f"DogecoinClient._call: Auth user={self.rpc_user}, has_pass={'yes' if self.rpc_pass else 'no'}" + ) response = requests.post( self.rpc_url, json=payload, auth=auth, # Basic auth for Dogecoin timeout=self.timeout, ) + logger.info( + f"DogecoinClient._call: HTTP response status={response.status_code}" + ) + + if response.status_code != 200: + logger.error( + f"DogecoinClient._call: HTTP {response.status_code} error response: {response.text}" + ) + response.raise_for_status() obj = response.json() if "error" in obj and obj["error"]: + logger.error( + f"DogecoinClient._call: RPC error response: {obj['error']}" + ) raise RuntimeError(f"RPC error: {obj['error']}") return obj.get("result") @@ -298,6 +319,12 @@ class DogecoinClient: def sendtoaddress(self, address: str, amount: float, comment: str = "") -> str: """Send Dogecoin to an address. Returns transaction ID.""" + import logging + + logger = logging.getLogger(__name__) + logger.info( + f"DogecoinClient.sendtoaddress called with: address={address}, amount={amount}, comment={comment}" + ) return self._call("sendtoaddress", [address, amount, comment]) def sendmany(self, from_label: str, addresses_amounts: Dict[str, float]) -> str: diff --git a/make_post_sell/lib/crypto_payment_rescue.py b/make_post_sell/lib/crypto_payment_rescue.py index 19a9ac0..950ec9f 100644 --- a/make_post_sell/lib/crypto_payment_rescue.py +++ b/make_post_sell/lib/crypto_payment_rescue.py @@ -262,12 +262,21 @@ class PaymentRescue: elif coin_type == "DOGE": # Dogecoin uses sendtoaddress RPC with coin amounts (not atomic units) - refund_amount_doge = float(refund_amount_coin) + # Round to 8 decimal places to match DOGE precision requirements + refund_amount_doge = round(float(refund_amount_coin), 8) logger.info( f"Calling Dogecoin sendtoaddress: {refund_amount_doge} DOGE to {refund_details['refund_address']}" ) + logger.info( + f"Dogecoin client config - URL: {self.crypto_client.rpc_url}, User: {self.crypto_client.rpc_user}" + ) + logger.info( + f"About to call sendtoaddress with params: address={refund_details['refund_address']}, amount={refund_amount_doge}" + ) tx_hash = self.crypto_client.sendtoaddress( - refund_details["refund_address"], refund_amount_doge + refund_details["refund_address"], + refund_amount_doge, + f"Overpayment refund for {refund_details['payment_id']}", ) tx_result = {"tx_hash": tx_hash} diff --git a/monero-sweep-all.sh b/monero-sweep-all.sh new file mode 100755 index 0000000..a8ae166 --- /dev/null +++ b/monero-sweep-all.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# Monero Sweep All Script +# Sweeps ALL unlocked funds from ALL Monero wallet accounts to cold storage +# Based on make_post_sell Makefile sweep-all target + +set -e # Exit on any error + +echo "=== SWEEPING ALL MONERO WALLET FUNDS TO COLD STORAGE ===" +echo "CAUTION: This will send ALL unlocked balance from ALL accounts!" +echo "" + +# Check if COLD_WALLET_ADDRESS is set +if [ -z "${COLD_WALLET_ADDRESS}" ]; then + echo "ERROR: COLD_WALLET_ADDRESS environment variable not set!" + echo "Usage: COLD_WALLET_ADDRESS= ./monero-sweep-all.sh" + exit 1 +fi + +# Check if wallet RPC is accessible +if ! curl --digest -u "test_user:test_pass" -s -X POST http://127.0.0.1:18083/json_rpc \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_version"}' >/dev/null 2>&1; then + echo "❌ Monero wallet RPC not accessible at http://127.0.0.1:18083" + echo "Start wallet RPC with:" + echo " make monero-wallet-remote-auth # For testing with auth" + echo " make monero-wallet-remote # For testing without auth" + echo " make monero-wallet # For production with local node" + exit 1 +fi + +echo "✓ Wallet RPC is accessible" +echo "" + +# Get all account balances +echo "Checking ALL account balances..." +all_balance=$(curl --digest -u "test_user:test_pass" -s -X POST http://127.0.0.1:18083/json_rpc \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_balance","params":{"all_accounts":true}}' | \ + python3 -c "import sys, json; d=json.load(sys.stdin); result = d.get('result', {}); total_balance = result.get('balance', 0); total_unlocked = result.get('unlocked_balance', 0); accounts = result.get('per_subaddress', []); print('Total balance: {} atomic units ({:.12f} XMR)'.format(total_balance, total_balance/1e12)); print('Total unlocked: {} atomic units ({:.12f} XMR)'.format(total_unlocked, total_unlocked/1e12)); print('Per account breakdown:'); [print(' Account {}:{} - Balance: {:.12f} XMR (unlocked: {:.12f} XMR)'.format(acc.get('account_index', 0), acc.get('address_index', 0), acc.get('balance', 0)/1e12, acc.get('unlocked_balance', 0)/1e12)) for acc in accounts if acc.get('balance', 0) > 0]; print(total_unlocked)") + +total_unlocked=$(echo "$all_balance" | tail -1) + +if [ -z "$total_unlocked" ] || [ "$total_unlocked" = "0" ]; then + echo "No unlocked balance to sweep across all accounts!" + exit 1 +fi + +echo "" +echo "Target address: ${COLD_WALLET_ADDRESS}" +echo "" + +# Confirmation prompt +read -p "Are you sure you want to sweep ALL unlocked funds from ALL accounts? (yes/no): " confirm +if [ "$confirm" != "yes" ]; then + echo "Cancelled." + exit 0 +fi + +echo "" +echo "Getting all accounts for sweeping..." + +# Get all account indices +accounts=$(curl --digest -u "test_user:test_pass" -s -X POST http://127.0.0.1:18083/json_rpc \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_accounts"}' | \ + python3 -c "import sys, json; d=json.load(sys.stdin); accounts = d.get('result', {}).get('subaddress_accounts', []); account_indices = [acc.get('account_index', 0) for acc in accounts]; print(','.join(map(str, account_indices)))") + +echo "Found accounts: $accounts" +echo "" + +# Sweep each account +for account in $(echo $accounts | tr ',' ' '); do + echo "Sweeping account $account..." + response=$(curl --digest -u "test_user:test_pass" -s -X POST http://127.0.0.1:18083/json_rpc \ + -H 'Content-Type: application/json' \ + -d "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"sweep_all\",\"params\":{\"address\":\"${COLD_WALLET_ADDRESS}\",\"account_index\":$account,\"subaddr_indices\":[],\"priority\":0,\"get_tx_metadata\":false}}") + + echo "$response" | python3 -c "import sys, json; d=json.load(sys.stdin); r=d.get('result',{}); amounts=r.get('amount_list',[]); fees=r.get('fee_list',[]); hashes=r.get('tx_hash_list',[]); [print('✓ Account $account: {:.12f} XMR (fee: {:.12f}) Hash: {}'.format(amt/1e12, fee/1e12, tx_hash)) for amt, fee, tx_hash in zip(amounts, fees, hashes)] if amounts else print('Account $account: No funds') if 'error' not in d else print('Account $account:', d['error'].get('message','Error'))" +done + +echo "" +echo "All account sweep operations completed!" +echo "" +echo "🔄 Check transaction status:" +echo " make monero-transactions" +echo "" +echo "💰 Verify cold wallet balance at:" +echo " https://xmrchain.net/search?value=${COLD_WALLET_ADDRESS}" \ No newline at end of file