You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
7.5 KiB
Python
155 lines
7.5 KiB
Python
import logging
|
|
from decimal import Decimal
|
|
from huey import crontab
|
|
from huey.contrib.djhuey import periodic_task
|
|
from django.conf import settings
|
|
from django.core.cache import cache
|
|
from core.monero import AuctionWallet, AuctionDaemon
|
|
from sales.models import ItemSale
|
|
|
|
|
|
logger = logging.getLogger('django.server')
|
|
|
|
def connect_rpc(rpc_type):
|
|
if rpc_type == "daemon":
|
|
rpc = AuctionDaemon()
|
|
elif rpc_type == "wallet":
|
|
rpc = AuctionWallet()
|
|
else:
|
|
logger.error('[ERROR] Invalid RPC type specified. Use "daemon" or "wallet"')
|
|
return False
|
|
if rpc.connected is False:
|
|
logging.error(f'[ERROR] Auction {rpc_type} is not connected. Stopping task.')
|
|
return False
|
|
return rpc
|
|
|
|
@periodic_task(crontab(minute='*/2'))
|
|
def poll_for_buyer_escrow_payments():
|
|
wallet_rpc = connect_rpc("wallet")
|
|
item_sales = ItemSale.objects.filter(payment_received=False)
|
|
for sale in item_sales:
|
|
logger.info(f'[INFO] Polling escrow address #{sale.escrow_account_index} for sale #{sale.id} for new funds.')
|
|
sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
|
|
tx_in = sale_account.incoming()
|
|
balances = sale_account.balances()
|
|
sale.received_payment_xmr = balances[0]
|
|
if balances[0] >= Decimal(str(sale.expected_payment_xmr)) and tx_in:
|
|
logger.info(f'[INFO] Found incoming transaction {tx_in[0].transaction} of {sale.received_payment_xmr} XMR for sale #{sale.id}.')
|
|
if tx_in[0].transaction.confirmations >= settings.BLOCK_CONFIRMATIONS_RCV:
|
|
logger.info(f'[INFO] The incoming transaction has {settings.BLOCK_CONFIRMATIONS_RCV} confirmations and enough funds. Marking payment received.')
|
|
sale.payment_received = True
|
|
else:
|
|
logger.info(f'[INFO] The incoming transaction only has {tx_in[0].transaction.confirmations} confirmations. Not enough to proceed.')
|
|
|
|
sale.save()
|
|
|
|
@periodic_task(crontab(minute='*/4'))
|
|
def pay_sellers_on_sold_items():
|
|
wallet_rpc = connect_rpc("wallet")
|
|
item_sales = ItemSale.objects.filter(item_received=True, payment_received=True).filter(seller_paid=False)
|
|
for sale in item_sales:
|
|
# Take platform fees from the sale - the 50:50 split between buyer/seller
|
|
sale_total = sale.agreed_price_xmr - sale.platform_fee_xmr
|
|
sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
|
|
logger.info(f'[INFO] Seller needs to be paid for sale #{sale.id}. Found balances of {sale_account.balances()} in account #{sale.escrow_account_index}.')
|
|
if sale_account.balances()[1] >= Decimal(str(sale.agreed_price_xmr)):
|
|
try:
|
|
# Construct a transaction so we can get current fee and subtract from the total
|
|
_tx = sale_account.transfer(
|
|
sale.item.payout_address, Decimal(.01), relay=False
|
|
)
|
|
new_total = sale_total - float(_tx[0].fee)
|
|
logger.info(f'[INFO] Sending {new_total} XMR from wallet account #{sale.escrow_account_index} to item owner\'s payout address for sale #{sale.id}.')
|
|
# Make the transaction with network fee removed
|
|
tx = sale_account.transfer(
|
|
sale.item.payout_address, new_total, relay=True
|
|
)
|
|
sale.network_fee_xmr = _tx[0].fee
|
|
sale.seller_payout_transaction = tx[0]
|
|
sale.seller_paid = True
|
|
sale.escrow_complete = True
|
|
sale.save()
|
|
except Exception as e:
|
|
logger.error(f'[ERROR] Unable to pay seller for sale #{sale.id}: ')
|
|
else:
|
|
logger.warning(f'[WARNING] Not enough unlocked funds available in account #{sale.escrow_account_index} for sale #{sale.id}.')
|
|
|
|
@periodic_task(crontab(minute='*/30'))
|
|
def pay_platform_on_sold_items():
|
|
wallet_rpc = connect_rpc("wallet")
|
|
aof = settings.PLATFORM_WALLET_ADDRESS
|
|
if aof is None:
|
|
aof = str(wallet_rpc.wallet.accounts[0].address())
|
|
|
|
item_sales = ItemSale.objects.filter(escrow_complete=True, seller_paid=True, item_received=True).filter(platform_paid=False)
|
|
for sale in item_sales:
|
|
logger.info(f'[INFO] Paying platform fees for sale #{sale.id} to wallet {aof}.')
|
|
sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
|
|
bal = sale_account.balances()[1]
|
|
if bal >= 0:
|
|
try:
|
|
if settings.PLATFORM_FEE_PERCENT > 0:
|
|
logger.info(f'[INFO] Getting platform fees of {bal} XMR')
|
|
sale_account.sweep_all(aof)
|
|
else:
|
|
logging.info('No platform fees are set - proceeding without taking fees.')
|
|
|
|
sale.platform_paid = True
|
|
sale.sale_finalized = True
|
|
sale.save()
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f'[ERROR] Unable to pay platform for sale #{sale.id} - trying again')
|
|
else:
|
|
logger.warning(f'[WARNING] Not enough unlocked funds available in account #{sale.escrow_account_index} for sale #{sale.id}.')
|
|
|
|
@periodic_task(crontab(minute='*/4'))
|
|
def refund_buyers_on_cancelled_sales() -> bool:
|
|
"""
|
|
Issue a refund to the buyer if they sent money but then cancelled the sale.
|
|
|
|
:rtype: bool
|
|
"""
|
|
wallet_rpc = connect_rpc("wallet")
|
|
item_sales = ItemSale.objects.filter(sale_cancelled=True, payment_refunded=False)
|
|
for sale in item_sales:
|
|
logger.info(f'[INFO] Refunding any sent funds from the buyer for sale #{sale.id}.')
|
|
sale_total = sale.agreed_price_xmr - sale.platform_fee_xmr
|
|
sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
|
|
balances = sale_account.balances()
|
|
logger.info(f'[INFO] Found balances of {balances} XMR for sale #{sale.id}.')
|
|
if balances[0] != balances[1]:
|
|
logger.info(f'[INFO] Balances not yet equal. Waiting')
|
|
return False
|
|
elif balances[1] > Decimal(0.0):
|
|
try:
|
|
# Construct a transaction so we can get current fee and subtract from the total
|
|
_tx = sale_account.transfer(
|
|
sale.bid.return_address, Decimal(.01), relay=False
|
|
)
|
|
new_total = sale_total - float(_tx[0].fee)
|
|
logger.info('[INFO] Refunding {new_total} XMR from wallet account #{sale.escrow_account_index} to buyer\'s return address for sale #{sale.id}.')
|
|
# Make the transaction with network fee removed
|
|
tx = sale_account.transfer(
|
|
sale.bid.return_address, new_total, relay=True
|
|
)
|
|
if tx:
|
|
sale.payment_refunded = True
|
|
sale.save()
|
|
logger.info(f'[INFO] Balance returned to buyer for sale #{sale.id}.')
|
|
return True
|
|
else:
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f'[ERROR] Unable to return funds to use for sale #{sale.id}: {e}')
|
|
return False
|
|
else:
|
|
if cache.get(f'{sale.id}_sale_refund_tries'):
|
|
logger.info(f'[INFO] Balance for sale #{sale.id} is 0 still. Marking payment_refunded flag.')
|
|
sale.payment_refunded = True
|
|
sale.save()
|
|
return True
|
|
else:
|
|
logger.info(f'[INFO] Setting flag in cache for sale #{sale.id} to try another block cycle for good measure.')
|
|
cache.set(f'{sale.id}_sale_refund_tries', {"tries": 1}, settings.CACHE_TTL)
|