mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 02:46:28 +00:00
Pay buyer onchain-tx
This commit is contained in:
parent
8f93c8f7b6
commit
efed6b3c0a
@ -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)
|
||||
|
@ -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"""
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
15
api/views.py
15
api/views.py
@ -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)
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user