Use dynamic fee estimation for crypto quote creation
- Add estimate_monero_fee_for_quote() using test transfer with do_not_relay - Add estimate_dogecoin_fee_for_quote() using estimatesmartfee RPC - Update Monero quote creation to use dynamic fees instead of hardcoded 0.0001 XMR - Update Dogecoin quote creation to use dynamic fees instead of hardcoded 0.01 DOGE - Maintains fallback to hardcoded fees if RPC calls fail - Should provide more accurate quotes and reduce dust further
This commit is contained in:
parent
87471f2290
commit
fa8ca79988
1 changed files with 78 additions and 20 deletions
|
|
@ -16,6 +16,8 @@ from . import (
|
|||
shop_is_ready_required,
|
||||
)
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import json
|
||||
import time
|
||||
import uuid as _uuid
|
||||
|
|
@ -143,13 +145,33 @@ def crypto_xmr_start(request):
|
|||
if usd_per_xmr is None:
|
||||
raise RuntimeError(f"Failed to fetch USD/XMR rate: {last_err}")
|
||||
|
||||
# Compute piconero owed with transaction fee buffer
|
||||
# Compute piconero owed with dynamic transaction fee estimation
|
||||
usd_total = float(invoice.total)
|
||||
xmr_amount = usd_total / usd_per_xmr
|
||||
base_amount_piconero = int(xmr_amount * 1_000_000_000_000)
|
||||
|
||||
# Add small fee buffer (default 0.0001 XMR) to cover transaction costs
|
||||
fee_buffer_xmr = float(settings.get("monero.fee_buffer", "0.0001"))
|
||||
xmr_amount_with_fee = xmr_amount + fee_buffer_xmr
|
||||
# Get dynamic fee estimate - we need the processor config first
|
||||
from ..models.crypto_processor import CryptoProcessor
|
||||
processor = (
|
||||
request.dbsession.query(CryptoProcessor)
|
||||
.filter(
|
||||
CryptoProcessor.shop_id == shop.id,
|
||||
CryptoProcessor.coin_type == "XMR",
|
||||
CryptoProcessor.enabled == True,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if processor and processor.sweep_to_address:
|
||||
# Use dynamic fee estimation with the actual sweep address
|
||||
fee_buffer_xmr = estimate_monero_fee_for_quote(
|
||||
settings, processor.sweep_to_address, base_amount_piconero
|
||||
)
|
||||
else:
|
||||
# Fallback to hardcoded fee if no processor configured yet
|
||||
fee_buffer_xmr = Decimal(settings.get("monero.fee_buffer", "0.0001"))
|
||||
|
||||
xmr_amount_with_fee = Decimal(str(xmr_amount)) + fee_buffer_xmr
|
||||
expected_piconero = int(xmr_amount_with_fee * 1_000_000_000_000)
|
||||
|
||||
# Quote expiry - use shop-specific setting
|
||||
|
|
@ -183,18 +205,7 @@ def crypto_xmr_start(request):
|
|||
else:
|
||||
confirmations_required = int(settings.get("monero.confirmations.high"))
|
||||
|
||||
# Get crypto processor configuration for this shop
|
||||
from ..models.crypto_processor import CryptoProcessor
|
||||
|
||||
processor = (
|
||||
request.dbsession.query(CryptoProcessor)
|
||||
.filter(
|
||||
CryptoProcessor.shop_id == shop.id,
|
||||
CryptoProcessor.coin_type == "XMR",
|
||||
CryptoProcessor.enabled == True,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
# processor already queried above for fee estimation
|
||||
|
||||
if not processor or processor.wallet_label is None:
|
||||
request.session.flash(("Shop has not configured Monero payments.", "error"))
|
||||
|
|
@ -368,13 +379,13 @@ def crypto_doge_start(request):
|
|||
if usd_per_doge is None:
|
||||
raise RuntimeError(f"Failed to fetch USD/DOGE rate: {last_err}")
|
||||
|
||||
# Compute koinu (smallest unit) owed with transaction fee buffer
|
||||
# Compute koinu (smallest unit) owed with dynamic transaction fee estimation
|
||||
usd_total = float(invoice.total)
|
||||
doge_amount = usd_total / usd_per_doge
|
||||
|
||||
# Add fee buffer (default 0.01 DOGE) to cover transaction costs
|
||||
fee_buffer_doge = float(settings.get("dogecoin.fee_buffer", "0.01"))
|
||||
doge_amount_with_fee = doge_amount + fee_buffer_doge
|
||||
# Use dynamic fee estimation for more accurate quotes
|
||||
fee_buffer_doge = estimate_dogecoin_fee_for_quote(settings)
|
||||
doge_amount_with_fee = Decimal(str(doge_amount)) + fee_buffer_doge
|
||||
expected_koinu = int(doge_amount_with_fee * 100_000_000) # Convert to koinu
|
||||
|
||||
# Quote expiry - use shop-specific setting
|
||||
|
|
@ -846,6 +857,53 @@ def crypto_quotes_history(request):
|
|||
}
|
||||
|
||||
|
||||
def estimate_monero_fee_for_quote(settings, shop_sweep_to_address, amount_piconero):
|
||||
"""Estimate Monero transaction fee for quote creation using dynamic RPC call."""
|
||||
try:
|
||||
client = get_client_from_settings(settings)
|
||||
|
||||
# We need a dummy subaddress for the test - use index 0 which should exist
|
||||
# This is just for fee estimation, not actual transaction
|
||||
test_transfer_result = client._call("transfer", {
|
||||
"destinations": [{
|
||||
"address": shop_sweep_to_address,
|
||||
"amount": amount_piconero
|
||||
}],
|
||||
"account_index": 0, # Use account 0 for estimation
|
||||
"priority": 1,
|
||||
"do_not_relay": True, # Don't broadcast, just estimate
|
||||
"get_tx_metadata": True
|
||||
})
|
||||
|
||||
estimated_fee_piconero = int(test_transfer_result.get("fee", 100000000000)) # Fallback to 0.0001 XMR
|
||||
return Decimal(estimated_fee_piconero) / Decimal('1000000000000') # Convert to XMR
|
||||
except Exception as e:
|
||||
# Fallback to hardcoded fee if RPC call fails
|
||||
fallback_fee = float(settings.get("monero.fee_buffer", "0.0001"))
|
||||
return Decimal(str(fallback_fee))
|
||||
|
||||
|
||||
def estimate_dogecoin_fee_for_quote(settings):
|
||||
"""Estimate Dogecoin transaction fee for quote creation using estimatesmartfee."""
|
||||
try:
|
||||
client = get_dogecoin_client_from_settings(settings)
|
||||
|
||||
# Use estimatesmartfee to get current network fee estimate
|
||||
fee_estimate_result = client._call("estimatesmartfee", [2]) # 2 block target
|
||||
if fee_estimate_result and "feerate" in fee_estimate_result:
|
||||
# feerate is in DOGE per KB, estimate transaction size as ~0.5KB
|
||||
estimated_fee_doge = float(fee_estimate_result["feerate"]) * 0.5
|
||||
return Decimal(str(estimated_fee_doge))
|
||||
else:
|
||||
# Fallback to hardcoded fee if estimatesmartfee fails
|
||||
fallback_fee = float(settings.get("dogecoin.fee_buffer", "0.01"))
|
||||
return Decimal(str(fallback_fee))
|
||||
except Exception as e:
|
||||
# Fallback to hardcoded fee if RPC call fails
|
||||
fallback_fee = float(settings.get("dogecoin.fee_buffer", "0.01"))
|
||||
return Decimal(str(fallback_fee))
|
||||
|
||||
|
||||
def get_payment_status_info(status):
|
||||
"""Get display information for payment status."""
|
||||
status_mapping = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue