Pay buyer onchain-tx

This commit is contained in:
Reckless_Satoshi 2022-06-16 08:31:30 -07:00
parent 8f93c8f7b6
commit efed6b3c0a
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 104 additions and 26 deletions

View File

@ -99,9 +99,11 @@ MIN_FLAT_ROUTING_FEE_LIMIT = 10
MIN_FLAT_ROUTING_FEE_LIMIT_REWARD = 2 MIN_FLAT_ROUTING_FEE_LIMIT_REWARD = 2
# Routing timeouts # Routing timeouts
REWARDS_TIMEOUT_SECONDS = 60 REWARDS_TIMEOUT_SECONDS = 60
PAYOUT_TIMEOUT_SECONDS = 60 PAYOUT_TIMEOUT_SECONDS = 90
# REVERSE SUBMARINE SWAP PAYOUTS # REVERSE SUBMARINE SWAP PAYOUTS
# Disable on-the-fly swaps feature
DISABLE_ONCHAIN = False
# Shape of fee to available liquidity curve. Either "linear" or "exponential" # Shape of fee to available liquidity curve. Either "linear" or "exponential"
SWAP_FEE_SHAPE = 'exponential' SWAP_FEE_SHAPE = 'exponential'
# EXPONENTIAL. fee (%) = MIN_SWAP_FEE + (MAX_SWAP_FEE - MIN_SWAP_FEE) * e ^ (-LAMBDA * onchain_liquidity_fraction) # EXPONENTIAL. fee (%) = MIN_SWAP_FEE + (MAX_SWAP_FEE - MIN_SWAP_FEE) * e ^ (-LAMBDA * onchain_liquidity_fraction)

View File

@ -1,4 +1,6 @@
import grpc, os, hashlib, secrets, ring import grpc, os, hashlib, secrets, ring
from robosats.api.models import OnchainPayment
from . import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub from . import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
from . import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub from . import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
from . import router_pb2 as routerrpc, router_pb2_grpc as routerstub from . import router_pb2 as routerrpc, router_pb2_grpc as routerstub
@ -70,7 +72,7 @@ class LNNode:
def estimate_fee(cls, amount_sats, target_conf=2, min_confs=1): def estimate_fee(cls, amount_sats, target_conf=2, min_confs=1):
"""Returns estimated fee for onchain payouts""" """Returns estimated fee for onchain payouts"""
# We assume segwit. Use robosats donation address (shortcut so there is no need to have user input) # We assume segwit. Use robosats donation address as shortcut so there is no need of user inputs
request = lnrpc.EstimateFeeRequest(AddrToAmount={'bc1q3cpp7ww92n6zp04hv40kd3eyy5avgughx6xqnx':amount_sats}, request = lnrpc.EstimateFeeRequest(AddrToAmount={'bc1q3cpp7ww92n6zp04hv40kd3eyy5avgughx6xqnx':amount_sats},
target_conf=target_conf, target_conf=target_conf,
min_confs=min_confs, min_confs=min_confs,
@ -112,6 +114,29 @@ class LNNode:
'unsettled_local_balance': response.unsettled_local_balance.sat, 'unsettled_local_balance': response.unsettled_local_balance.sat,
'unsettled_remote_balance': response.unsettled_remote_balance.sat} 'unsettled_remote_balance': response.unsettled_remote_balance.sat}
@classmethod
def pay_onchain(cls, onchainpayment):
"""Send onchain transaction for buyer payouts"""
if bool(config("DISABLE_ONCHAIN")):
return False
request = lnrpc.SendCoinsRequest(addr=onchainpayment.address,
amount=int(onchainpayment.sent_satoshis),
sat_per_vbyte=int(onchainpayment.mining_fee_rate),
label=str("Payout order #" + str(onchainpayment.order_paid_TX.id)),
spend_unconfirmed=True)
response = cls.lightningstub.SendCoins(request,
metadata=[("macaroon",
MACAROON.hex())])
print(response)
onchainpayment.txid = response.txid
onchainpayment.status = OnchainPayment.Status.MEMPO
onchainpayment.save()
return True
@classmethod @classmethod
def cancel_return_hold_invoice(cls, payment_hash): def cancel_return_hold_invoice(cls, payment_hash):
"""Cancels or returns a hold invoice""" """Cancels or returns a hold invoice"""

View File

@ -589,12 +589,17 @@ class Logics:
context["swap_failure_reason"] = "Order amount is too small to be eligible for a swap" context["swap_failure_reason"] = "Order amount is too small to be eligible for a swap"
return True, context return True, context
if not bool(config("DISABLE_ONCHAIN")):
context["swap_allowed"] = False
context["swap_failure_reason"] = "On-the-fly submarine swaps are dissabled"
return True, context
if order.payout_tx == None: if order.payout_tx == None:
# Creates the OnchainPayment object and checks node balance # Creates the OnchainPayment object and checks node balance
valid = cls.create_onchain_payment(order, user, preliminary_amount=context["invoice_amount"]) valid = cls.create_onchain_payment(order, user, preliminary_amount=context["invoice_amount"])
if not valid: if not valid:
context["swap_allowed"] = False context["swap_allowed"] = False
context["swap_failure_reason"] = "Not enough onchain liquidity available to offer swaps" context["swap_failure_reason"] = "Not enough onchain liquidity available to offer a SWAP"
return True, context return True, context
context["swap_allowed"] = True context["swap_allowed"] = True
@ -1246,7 +1251,6 @@ class Logics:
def settle_escrow(order): def settle_escrow(order):
"""Settles the trade escrow hold invoice""" """Settles the trade escrow hold invoice"""
# TODO ERROR HANDLING
if LNNode.settle_hold_invoice(order.trade_escrow.preimage): if LNNode.settle_hold_invoice(order.trade_escrow.preimage):
order.trade_escrow.status = LNPayment.Status.SETLED order.trade_escrow.status = LNPayment.Status.SETLED
order.trade_escrow.save() order.trade_escrow.save()
@ -1254,7 +1258,6 @@ class Logics:
def settle_bond(bond): def settle_bond(bond):
"""Settles the bond hold invoice""" """Settles the bond hold invoice"""
# TODO ERROR HANDLING
if LNNode.settle_hold_invoice(bond.preimage): if LNNode.settle_hold_invoice(bond.preimage):
bond.status = LNPayment.Status.SETLED bond.status = LNPayment.Status.SETLED
bond.save() bond.save()
@ -1310,6 +1313,30 @@ class Logics:
else: else:
raise e raise e
@classmethod
def pay_buyer(cls, order):
'''Pays buyer invoice or onchain address'''
# Pay to buyer invoice
if not order.is_swap:
##### Background process "follow_invoices" will try to pay this invoice until success
order.status = Order.Status.PAY
order.payout.status = LNPayment.Status.FLIGHT
order.payout.save()
order.save()
send_message.delay(order.id,'trade_successful')
return True
# Pay onchain to address
else:
valid = LNNode.pay_onchain(order.payout_tx)
if valid:
order.status = Order.Status.SUC
order.save()
send_message.delay(order.id,'trade_successful')
return True
return False
@classmethod @classmethod
def confirm_fiat(cls, order, user): def confirm_fiat(cls, order, user):
"""If Order is in the CHAT states: """If Order is in the CHAT states:
@ -1318,7 +1345,7 @@ class Logics:
if (order.status == Order.Status.CHA if (order.status == Order.Status.CHA
or order.status == Order.Status.FSE or order.status == Order.Status.FSE
): # TODO Alternatively, if all collateral is locked? test out ):
# If buyer, settle escrow and mark fiat sent # If buyer, settle escrow and mark fiat sent
if cls.is_buyer(order, user): if cls.is_buyer(order, user):
@ -1334,30 +1361,24 @@ class Logics:
} }
# Make sure the trade escrow is at least as big as the buyer invoice # Make sure the trade escrow is at least as big as the buyer invoice
if order.trade_escrow.num_satoshis <= order.payout.num_satoshis: num_satoshis = order.payout_tx.num_satoshis if order.is_swap else order.payout.num_satoshis
if order.trade_escrow.num_satoshis <= num_satoshis:
return False, { return False, {
"bad_request": "bad_request":
"Woah, something broke badly. Report in the public channels, or open a Github Issue." "Woah, something broke badly. Report in the public channels, or open a Github Issue."
} }
if cls.settle_escrow( # !!! KEY LINE - SETTLES THE TRADE ESCROW !!!
order if cls.settle_escrow(order):
): ##### !!! KEY LINE - SETTLES THE TRADE ESCROW !!!
order.trade_escrow.status = LNPayment.Status.SETLED order.trade_escrow.status = LNPayment.Status.SETLED
# Double check the escrow is settled. # Double check the escrow is settled.
if LNNode.double_check_htlc_is_settled( if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
order.trade_escrow.payment_hash): # RETURN THE BONDS
# RETURN THE BONDS // Probably best also do it even if payment failed
cls.return_bond(order.taker_bond) cls.return_bond(order.taker_bond)
cls.return_bond(order.maker_bond) cls.return_bond(order.maker_bond)
##### !!! KEY LINE - PAYS THE BUYER INVOICE !!! ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
##### Background process "follow_invoices" will try to pay this invoice until success cls.pay_buyer(order)
order.status = Order.Status.PAY
order.payout.status = LNPayment.Status.FLIGHT
order.payout.save()
order.save()
send_message.delay(order.id,'trade_successful')
# Add referral rewards (safe) # Add referral rewards (safe)
try: try:

View File

@ -220,7 +220,7 @@ class OnchainPayment(models.Model):
null=False, null=False,
blank=False) blank=False)
mining_fee_rate = models.DecimalField(max_digits=6, mining_fee_rate = models.DecimalField(max_digits=6,
decimal_places=3, decimal_places=3,
default=1.05, default=1.05,
null=False, null=False,
blank=False) blank=False)

View File

@ -12,7 +12,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User from django.contrib.auth.models import User
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer
from api.models import LNPayment, MarketTick, Order, Currency, Profile from api.models import LNPayment, MarketTick, OnchainPayment, Order, Currency, Profile
from control.models import AccountingDay from control.models import AccountingDay
from api.logics import Logics from api.logics import Logics
from api.messages import Telegram from api.messages import Telegram
@ -399,6 +399,19 @@ class OrderView(viewsets.ViewSet):
if order.status == Order.Status.EXP: if order.status == Order.Status.EXP:
data["expiry_reason"] = order.expiry_reason data["expiry_reason"] = order.expiry_reason
data["expiry_message"] = Order.ExpiryReasons(order.expiry_reason).label data["expiry_message"] = Order.ExpiryReasons(order.expiry_reason).label
# If status is 'Succes' add final stats and txid if it is a swap
if order.status == Order.Status.SUC:
# TODO: add summary of order for buyer/sellers: sats in/out, fee paid, total time? etc
# If buyer and is a swap, add TXID
if Logics.is_buyer(order,request.user):
if order.is_swap:
data["num_satoshis"] = order.payout_tx.num_satoshis
data["sent_satoshis"] = order.payout_tx.sent_satoshis
if order.payout_tx.status in [OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]:
data["txid"] = order.payout_tx.txid
return Response(data, status.HTTP_200_OK) return Response(data, status.HTTP_200_OK)

View File

@ -1228,19 +1228,36 @@ handleRatingRobosatsChange=(e)=>{
{this.state.rating_platform==5 ? {this.state.rating_platform==5 ?
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Typography variant="body2" align="center"> <Typography variant="body2" align="center">
<p><b>{t("Thank you! RoboSats loves you too ❤️")}</b></p> <b>{t("Thank you! RoboSats loves you too ❤️")}</b>
<p>{t("RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!")}</p> </Typography>
<Typography variant="body2" align="center">
{t("RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!")}
</Typography> </Typography>
</Grid> </Grid>
: null} : null}
{this.state.rating_platform!=5 & this.state.rating_platform!=null ? {this.state.rating_platform!=5 & this.state.rating_platform!=null ?
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Typography variant="body2" align="center"> <Typography variant="body2" align="center">
<p><b>{t("Thank you for using Robosats!")}</b></p> <b>{t("Thank you for using Robosats!")}</b>
<p><Trans i18nKey="let_us_know_hot_to_improve">Let us know how the platform could improve (<Link target='_blank' href="https://t.me/robosats">Telegram</Link> / <Link target='_blank' href="https://github.com/Reckless-Satoshi/robosats/issues">Github</Link>)</Trans></p> </Typography>
<Typography variant="body2" align="center">
<Trans i18nKey="let_us_know_hot_to_improve">Let us know how the platform could improve (<Link target='_blank' href="https://t.me/robosats">Telegram</Link> / <Link target='_blank' href="https://github.com/Reckless-Satoshi/robosats/issues">Github</Link>)</Trans>
</Typography> </Typography>
</Grid> </Grid>
: null} : null}
{/* SHOW TXID IF USER RECEIVES ONCHAIN */}
{this.props.data.txid ?
<Grid item xs={12} align="center">
<Typography variant="body2" align="center">
<b>{t("Your TXID:")}</b>
</Typography>
<Typography variant="body2" align="center">
<Link target='_blank' href={"http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/tx/"+this.props.data.txid}>{this.props.data.txid}</Link>
</Typography>
</Grid>
: null}
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button color='primary' onClick={() => {this.props.push('/')}}>{t("Start Again")}</Button> <Button color='primary' onClick={() => {this.props.push('/')}}>{t("Start Again")}</Button>
</Grid> </Grid>