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
# Routing timeouts
REWARDS_TIMEOUT_SECONDS = 60
PAYOUT_TIMEOUT_SECONDS = 60
PAYOUT_TIMEOUT_SECONDS = 90
# REVERSE SUBMARINE SWAP PAYOUTS
# Disable on-the-fly swaps feature
DISABLE_ONCHAIN = False
# Shape of fee to available liquidity curve. Either "linear" or "exponential"
SWAP_FEE_SHAPE = 'exponential'
# 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
from robosats.api.models import OnchainPayment
from . import lightning_pb2 as lnrpc, lightning_pb2_grpc as lightningstub
from . import invoices_pb2 as invoicesrpc, invoices_pb2_grpc as invoicesstub
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):
"""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},
target_conf=target_conf,
min_confs=min_confs,
@ -112,6 +114,29 @@ class LNNode:
'unsettled_local_balance': response.unsettled_local_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
def cancel_return_hold_invoice(cls, payment_hash):
"""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"
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:
# Creates the OnchainPayment object and checks node balance
valid = cls.create_onchain_payment(order, user, preliminary_amount=context["invoice_amount"])
if not valid:
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
context["swap_allowed"] = True
@ -1246,7 +1251,6 @@ class Logics:
def settle_escrow(order):
"""Settles the trade escrow hold invoice"""
# TODO ERROR HANDLING
if LNNode.settle_hold_invoice(order.trade_escrow.preimage):
order.trade_escrow.status = LNPayment.Status.SETLED
order.trade_escrow.save()
@ -1254,7 +1258,6 @@ class Logics:
def settle_bond(bond):
"""Settles the bond hold invoice"""
# TODO ERROR HANDLING
if LNNode.settle_hold_invoice(bond.preimage):
bond.status = LNPayment.Status.SETLED
bond.save()
@ -1310,6 +1313,30 @@ class Logics:
else:
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
def confirm_fiat(cls, order, user):
"""If Order is in the CHAT states:
@ -1318,7 +1345,7 @@ class Logics:
if (order.status == Order.Status.CHA
or order.status == Order.Status.FSE
): # TODO Alternatively, if all collateral is locked? test out
):
# If buyer, settle escrow and mark fiat sent
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
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, {
"bad_request":
"Woah, something broke badly. Report in the public channels, or open a Github Issue."
}
if cls.settle_escrow(
order
): ##### !!! KEY LINE - SETTLES THE TRADE ESCROW !!!
# !!! KEY LINE - SETTLES THE TRADE ESCROW !!!
if cls.settle_escrow(order):
order.trade_escrow.status = LNPayment.Status.SETLED
# Double check the escrow is settled.
if LNNode.double_check_htlc_is_settled(
order.trade_escrow.payment_hash):
# RETURN THE BONDS // Probably best also do it even if payment failed
if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
# RETURN THE BONDS
cls.return_bond(order.taker_bond)
cls.return_bond(order.maker_bond)
##### !!! KEY LINE - PAYS THE BUYER INVOICE !!!
##### 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')
cls.pay_buyer(order)
# Add referral rewards (safe)
try:

View File

@ -220,7 +220,7 @@ class OnchainPayment(models.Model):
null=False,
blank=False)
mining_fee_rate = models.DecimalField(max_digits=6,
decimal_places=3,
decimal_places=3,
default=1.05,
null=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 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 api.logics import Logics
from api.messages import Telegram
@ -399,6 +399,19 @@ class OrderView(viewsets.ViewSet):
if order.status == Order.Status.EXP:
data["expiry_reason"] = order.expiry_reason
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)

View File

@ -1228,19 +1228,36 @@ handleRatingRobosatsChange=(e)=>{
{this.state.rating_platform==5 ?
<Grid item xs={12} align="center">
<Typography variant="body2" align="center">
<p><b>{t("Thank you! RoboSats loves you too ❤️")}</b></p>
<p>{t("RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!")}</p>
<b>{t("Thank you! RoboSats loves you too ❤️")}</b>
</Typography>
<Typography variant="body2" align="center">
{t("RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!")}
</Typography>
</Grid>
: null}
{this.state.rating_platform!=5 & this.state.rating_platform!=null ?
<Grid item xs={12} align="center">
<Typography variant="body2" align="center">
<p><b>{t("Thank you for using Robosats!")}</b></p>
<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>
<b>{t("Thank you for using Robosats!")}</b>
</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>
</Grid>
: 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">
<Button color='primary' onClick={() => {this.props.push('/')}}>{t("Start Again")}</Button>
</Grid>