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.
This commit is contained in:
Russell Ballestrini 2025-09-29 13:52:37 -04:00
parent b5930694c7
commit a3c20609c0
3 changed files with 126 additions and 2 deletions

View file

@ -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:

View file

@ -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}

88
monero-sweep-all.sh Executable file
View file

@ -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=<your-xmr-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}"