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
|
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)
|
||||||
|
@ -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"""
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user