Fix auto-sweep bug for locked Monero funds
- Fix critical bug where payments were incorrectly marked as 'swept' when funds were still locked - Auto-sweep now properly retries until funds unlock (after Monero's 10-block lock time) - Fix test expectation for expired-refunded status (hyphen not underscore)
This commit is contained in:
parent
13eb2abbb2
commit
4796d83eae
3 changed files with 24 additions and 28 deletions
|
|
@ -94,17 +94,13 @@ def auto_sweep_payment_xmr(client, crypto_payment: CryptoPayment, dbsession=None
|
|||
# Calculate sweep amount for THIS SPECIFIC payment only
|
||||
payment_amount_xmr = Decimal(crypto_payment.received_amount) / atomic_units
|
||||
|
||||
# If no balance, mark as already swept (another payment swept it)
|
||||
# If no unlocked balance, funds are still locked (10-block lock time)
|
||||
if unlocked_balance == 0:
|
||||
logger.info(
|
||||
f"No balance to sweep for payment {crypto_payment.id} - marking as swept"
|
||||
f"No unlocked balance for payment {crypto_payment.id} - funds still locked, will retry later"
|
||||
)
|
||||
crypto_payment.swept_amount = crypto_payment.received_amount
|
||||
crypto_payment.swept_tx_hash = "pooled_sweep"
|
||||
crypto_payment.swept_timestamp = now_timestamp()
|
||||
if dbsession:
|
||||
dbsession.add(crypto_payment)
|
||||
return True
|
||||
# Don't mark as swept - funds are just locked!
|
||||
return False
|
||||
|
||||
# Sweep all available balance minus a small buffer for fees
|
||||
# Use "sweep_all" instead of exact amount to handle fees automatically
|
||||
|
|
|
|||
|
|
@ -157,28 +157,16 @@
|
|||
cryptoAddress.style.display = 'none';
|
||||
}
|
||||
|
||||
// Disable payment buttons but keep them visible for context
|
||||
// Remove all payment buttons completely
|
||||
if (buttonGrid) {
|
||||
const copyAddrBtn = document.getElementById('copy-addr');
|
||||
const copyAmtBtn = document.getElementById('copy-amt');
|
||||
const walletLink = buttonGrid.querySelector('a[href^="monero:"], a[href^="dogecoin:"]');
|
||||
|
||||
[copyAddrBtn, copyAmtBtn].forEach(btn => {
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.5';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
btn.style.background = '#ccc';
|
||||
btn.textContent = btn.textContent.replace('Copy', 'Payment Received');
|
||||
}
|
||||
});
|
||||
|
||||
if (walletLink) {
|
||||
walletLink.style.opacity = '0.5';
|
||||
walletLink.style.pointerEvents = 'none';
|
||||
walletLink.style.background = '#ccc';
|
||||
walletLink.textContent = 'Payment Received';
|
||||
}
|
||||
// Remove the buttons
|
||||
if (copyAddrBtn) copyAddrBtn.remove();
|
||||
if (copyAmtBtn) copyAmtBtn.remove();
|
||||
if (walletLink) walletLink.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,6 +231,12 @@
|
|||
// Payment received - remove payment elements to prevent double-sending
|
||||
removePaymentElements();
|
||||
|
||||
// Hide cancel button - can't cancel after receiving funds
|
||||
const cancelBtn = document.getElementById('cancel-btn');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Hide countdown since payment is received
|
||||
if (expiresEl) {
|
||||
expiresEl.parentNode.style.display = 'none';
|
||||
|
|
@ -364,9 +358,15 @@
|
|||
// Handle different status transitions
|
||||
if (currentStatus === 'received') {
|
||||
// Payment received - remove payment elements to prevent double-sending
|
||||
// but keep polling for confirmation updates
|
||||
// and hide cancel button since funds were received
|
||||
removePaymentElements();
|
||||
|
||||
// Hide cancel button - can't cancel after receiving funds
|
||||
const cancelBtn = document.getElementById('cancel-btn');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// Hide countdown since payment is received
|
||||
if (expiresEl && expiresEl.parentNode) {
|
||||
expiresEl.parentNode.style.display = 'none';
|
||||
|
|
|
|||
|
|
@ -603,8 +603,8 @@ class CryptoWatcherIntegrationTests(unittest.TestCase):
|
|||
|
||||
process_payment(mock_request, payment, transfers, client=mock_client)
|
||||
|
||||
# Payment should be expired_refunded (refund succeeded)
|
||||
self.assertEqual(payment.status, "expired_refunded")
|
||||
# Payment should be expired-refunded (refund succeeded)
|
||||
self.assertEqual(payment.status, "expired-refunded")
|
||||
|
||||
# Refund should be attempted
|
||||
mock_rescue.handle_expired_payment.assert_called_once()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue