diff --git a/.env-sample b/.env-sample
index ee4b7a66..70a6db73 100644
--- a/.env-sample
+++ b/.env-sample
@@ -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)
diff --git a/api/lightning/node.py b/api/lightning/node.py
index ecc555a0..96ad7e69 100644
--- a/api/lightning/node.py
+++ b/api/lightning/node.py
@@ -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"""
diff --git a/api/logics.py b/api/logics.py
index 9896cfa1..6bd29e8b 100644
--- a/api/logics.py
+++ b/api/logics.py
@@ -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:
diff --git a/api/models.py b/api/models.py
index f7d02e2d..c1fde799 100644
--- a/api/models.py
+++ b/api/models.py
@@ -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)
diff --git a/api/views.py b/api/views.py
index 78ba2049..1365e60e 100644
--- a/api/views.py
+++ b/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)
diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js
index 68aa7bc5..6b6781be 100644
--- a/frontend/src/components/TradeBox.js
+++ b/frontend/src/components/TradeBox.js
@@ -1228,19 +1228,36 @@ handleRatingRobosatsChange=(e)=>{
{this.state.rating_platform==5 ?
-
{t("Thank you! RoboSats loves you too ❤️")}
-
{t("RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!")}
+ {t("Thank you! RoboSats loves you too ❤️")}
+
+
+ {t("RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!")}
: null}
{this.state.rating_platform!=5 & this.state.rating_platform!=null ?
-
{t("Thank you for using Robosats!")}
-
Let us know how the platform could improve (Telegram / Github)
+ {t("Thank you for using Robosats!")}
+
+
+ Let us know how the platform could improve (Telegram / Github)
: null}
+
+ {/* SHOW TXID IF USER RECEIVES ONCHAIN */}
+ {this.props.data.txid ?
+
+
+ {t("Your TXID:")}
+
+
+ {this.props.data.txid}
+
+
+ : null}
+