Skip to content

Commit

Permalink
Update LNpayment model and logics to use user's routing budget
Browse files Browse the repository at this point in the history
  • Loading branch information
Reckless-Satoshi committed Nov 23, 2022
1 parent 917d0a7 commit d002098
Show file tree
Hide file tree
Showing 12 changed files with 68 additions and 36 deletions.
9 changes: 4 additions & 5 deletions api/lightning/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def resetmc(cls):
return True

@classmethod
def validate_ln_invoice(cls, invoice, num_satoshis):
def validate_ln_invoice(cls, invoice, num_satoshis, routing_budget_ppm):
"""Checks if the submited LN invoice comforms to expectations"""

payout = {
Expand All @@ -283,9 +283,8 @@ def validate_ln_invoice(cls, invoice, num_satoshis):
route_hints = payreq_decoded.route_hints

# Max amount RoboSats will pay for routing
max_routing_fee_sats = max(
num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT_REWARD")),
max_routing_fee_sats = int(
float(num_satoshis) * float(routing_budget_ppm) / 1000000
)

if route_hints:
Expand All @@ -306,7 +305,7 @@ def validate_ln_invoice(cls, invoice, num_satoshis):
# If the cheapest possible private route is more expensive than what RoboSats is willing to pay
if min(routes_cost) >= max_routing_fee_sats:
payout["context"] = {
"bad_invoice": "The invoice submitted only has a trick on the routing hints, you might be using an incompatible wallet (probably Muun? Use an onchain address instead!). Check the wallet compatibility guide at wallets.robosats.com"
"bad_invoice": "The invoice hinted private routes are not payable within the submitted routing budget."
}
return payout

Expand Down
18 changes: 13 additions & 5 deletions api/logics.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ def update_address(cls, order, user, address, mining_fee_rate):
return True, None

@classmethod
def update_invoice(cls, order, user, invoice):
def update_invoice(cls, order, user, invoice, routing_budget_ppm):

# Empty invoice?
if not invoice:
Expand Down Expand Up @@ -754,7 +754,11 @@ def update_invoice(cls, order, user, invoice):
cls.cancel_onchain_payment(order)

num_satoshis = cls.payout_amount(order, user)[1]["invoice_amount"]
payout = LNNode.validate_ln_invoice(invoice, num_satoshis)
routing_budget_sats = float(num_satoshis) * (
float(routing_budget_ppm) / 1000000
)
num_satoshis = int(num_satoshis - routing_budget_sats)
payout = LNNode.validate_ln_invoice(invoice, num_satoshis, routing_budget_ppm)

if not payout["valid"]:
return False, payout["context"]
Expand All @@ -765,6 +769,8 @@ def update_invoice(cls, order, user, invoice):
sender=User.objects.get(username=ESCROW_USERNAME),
order_paid_LN=order, # In case this user has other payouts, update the one related to this order.
receiver=user,
routing_budget_ppm=routing_budget_ppm,
routing_budget_sats=routing_budget_sats,
# if there is a LNPayment matching these above, it updates that one with defaults below.
defaults={
"invoice": invoice,
Expand Down Expand Up @@ -1679,7 +1685,9 @@ def summarize_trade(cls, order, user):
else:
summary["received_sats"] = order.payout.num_satoshis
summary["trade_fee_sats"] = round(
order.last_satoshis - summary["received_sats"]
order.last_satoshis
- summary["received_sats"]
- order.payout.routing_budget_sats
)
# Only add context for swap costs if the user is the swap recipient. Peer should not know whether it was a swap
if users[order_user] == user and order.is_swap:
Expand Down Expand Up @@ -1716,11 +1724,11 @@ def summarize_trade(cls, order, user):
order.contract_finalization_time - order.last_satoshis_time
)
if not order.is_swap:
platform_summary["routing_fee_sats"] = order.payout.fee
platform_summary["routing_budget_sats"] = order.payout.routing_budget_sats
platform_summary["trade_revenue_sats"] = int(
order.trade_escrow.num_satoshis
- order.payout.num_satoshis
- order.payout.fee
- order.payout.routing_budget_sats
)
else:
platform_summary["routing_fee_sats"] = 0
Expand Down
13 changes: 13 additions & 0 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,19 @@ class FailureReason(models.IntegerChoices):
MaxValueValidator(1.5 * MAX_TRADE),
]
)
# Routing budget in PPM
routing_budget = models.PositiveBigIntegerField(
default=0,
null=False,
validators=[
MinValueValidator(0),
MaxValueValidator(100000),
],
)
# Routing budget in Sats. Only for reporting summaries.
routing_budget_sats = models.DecimalField(
max_digits=10, decimal_places=3, default=0, null=False, blank=False
)
# Fee in sats with mSats decimals fee_msat
fee = models.DecimalField(
max_digits=10, decimal_places=3, default=0, null=False, blank=False
Expand Down
8 changes: 8 additions & 0 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,14 @@ class UpdateOrderSerializer(serializers.Serializer):
invoice = serializers.CharField(
max_length=2000, allow_null=True, allow_blank=True, default=None
)
routing_budget_ppm = serializers.IntegerField(
default=0,
min_value=0,
max_value=100000,
allow_null=True,
required=False,
help_text="Max budget to allocate for routing in PPM",
)
address = serializers.CharField(
max_length=100, allow_null=True, allow_blank=True, default=None
)
Expand Down
9 changes: 3 additions & 6 deletions api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,10 @@ def follow_send_payment(hash):
from api.models import LNPayment, Order

lnpayment = LNPayment.objects.get(payment_hash=hash)
# Defaults is 0ppm. Set by the user over API. Defaults to 1000 ppm on ReactJS frontend.
fee_limit_sat = int(
max(
lnpayment.num_satoshis * float(config("PROPORTIONAL_ROUTING_FEE_LIMIT")),
float(config("MIN_FLAT_ROUTING_FEE_LIMIT")),
)
) # 1000 ppm or 10 sats
float(lnpayment.num_satoshis) * float(lnpayment.routing_budget_ppm) / 1000000
)
timeout_seconds = int(config("PAYOUT_TIMEOUT_SECONDS"))

request = LNNode.routerrpc.SendPaymentRequest(
Expand Down Expand Up @@ -145,7 +143,6 @@ def follow_send_payment(hash):
],
"IN_FLIGHT": False,
}
print(context)

# If failed due to not route, reset mission control. (This won't scale well, just a temporary fix)
# ResetMC deactivate temporary for tests
Expand Down
5 changes: 4 additions & 1 deletion api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ def take_update_confirm_dispute_cancel(self, request, format=None):
# 5.b)'update_address' 6)'submit_statement' (in dispute), 7)'rate_user' , 8)'rate_platform'
action = serializer.data.get("action")
invoice = serializer.data.get("invoice")
routing_budget_ppm = serializer.data.get("routing_budget_ppm", 0)
address = serializer.data.get("address")
mining_fee_rate = serializer.data.get("mining_fee_rate")
statement = serializer.data.get("statement")
Expand Down Expand Up @@ -543,7 +544,9 @@ def take_update_confirm_dispute_cancel(self, request, format=None):

# 2) If action is 'update invoice'
elif action == "update_invoice":
valid, context = Logics.update_invoice(order, request.user, invoice)
valid, context = Logics.update_invoice(
order, request.user, invoice, routing_budget_ppm
)
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ services:
environment:
TOR_PROXY_IP: 127.0.0.1
TOR_PROXY_PORT: 9050
ROBOSATS_ONION: robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion
ROBOSATS_ONION: robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion
network_mode: service:tor
volumes:
- ./frontend/static:/usr/src/robosats/static
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/components/Notifications/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { StringIfPlural, useTranslation } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import {
Tooltip,
Alert,
Expand Down Expand Up @@ -262,7 +262,6 @@ const Notifications = ({
} else if (order?.is_seller && status > 7 && oldStatus < 7) {
message = Messages.escrowLocked;
} else if ([9, 10].includes(status) && oldStatus < 9) {
console.log('yoooo');
message = Messages.chat;
} else if (order?.is_seller && [13, 14, 15].includes(status) && oldStatus < 13) {
message = Messages.successful;
Expand Down Expand Up @@ -333,7 +332,6 @@ const Notifications = ({
return (
<StyledTooltip
open={show}
style={{ padding: 0, backgroundColor: 'black' }}
placement={windowWidth > 60 ? 'left' : 'bottom'}
title={
<Alert
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/components/TradeBox/Forms/LightningPayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,13 @@ export const LightningPayoutForm = ({
const [loadingLnproxy, setLoadingLnproxy] = useState<boolean>(false);

const computeInvoiceAmount = function () {
//const tradeAmount = order.trade_satoshis
const tradeAmount = 1000000;
return tradeAmount - Math.floor((tradeAmount / 1000000) * lightning.routingBudgetPPM);
const tradeAmount = order.trade_satoshis;
return Math.floor(tradeAmount - tradeAmount * (lightning.routingBudgetPPM / 1000000));
};

const validateInvoice = function (invoice: string, targetAmount: number) {
const invoiceAmount = Number(invoice.substring(4, 5 + Math.floor(Math.log10(targetAmount))));
if (targetAmount != invoiceAmount) {
if (targetAmount != invoiceAmount && invoice.length > 20) {
return 'Invalid invoice amount';
} else {
return '';
Expand Down Expand Up @@ -196,20 +195,23 @@ export const LightningPayoutForm = ({
};

const onRoutingBudgetChange = function (e) {
const tradeAmount = order.trade_satoshis;
if (isFinite(e.target.value) && e.target.value >= 0) {
let routingBudgetSats;
let routingBudgetPPM;

if (lightning.routingBudgetUnit === 'Sats') {
routingBudgetSats = Math.floor(e.target.value);
routingBudgetPPM = Math.round((routingBudgetSats * 1000000) / lightning.amount);
routingBudgetPPM = Math.round((routingBudgetSats * 1000000) / tradeAmount);
} else {
routingBudgetPPM = e.target.value;
routingBudgetSats = Math.ceil((lightning.amount / 1000000) * routingBudgetPPM);
}

if (routingBudgetPPM < 99999) {
const amount = lightning.amount - routingBudgetSats;
const amount = Math.floor(
tradeAmount - tradeAmount * (lightning.routingBudgetPPM / 1000000),
);
setLightning({ ...lightning, routingBudgetSats, routingBudgetPPM, amount });
}
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/TradeBox/TradeSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,9 @@ const TradeSummary = ({
</ListItemIcon>
<ListItemText
primary={t('{{routingFeeSats}} MiliSats', {
routingFeeSats: pn(platformSummary.routing_fee_sats),
routingFeeSats: pn(platformSummary.routing_budget_sats),
})}
secondary={t('Platform covered routing fee')}
secondary={t('Routing budget')}
/>
</ListItem>

Expand Down
16 changes: 10 additions & 6 deletions frontend/src/components/TradeBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const TradeBox = ({
| 'submit_statement'
| 'rate_platform';
invoice?: string;
routing_budget?: number;
routing_budget_ppm?: number;
address?: string;
mining_fee_rate?: number;
statement?: string;
Expand All @@ -144,7 +144,7 @@ const TradeBox = ({
const submitAction = function ({
action,
invoice,
routing_budget,
routing_budget_ppm,
address,
mining_fee_rate,
statement,
Expand All @@ -154,7 +154,7 @@ const TradeBox = ({
.post(baseUrl, '/api/order/?order_id=' + order.id, {
action,
invoice,
routing_budget,
routing_budget_ppm,
address,
mining_fee_rate,
statement,
Expand Down Expand Up @@ -204,7 +204,11 @@ const TradeBox = ({

const updateInvoice = function (invoice: string) {
setLoadingButtons({ ...noLoadingButtons, submitInvoice: true });
submitAction({ action: 'update_invoice', invoice, routing_budget: lightning.routingBudgetPPM });
submitAction({
action: 'update_invoice',
invoice,
routing_budget_ppm: lightning.routingBudgetPPM,
});
};

const updateAddress = function () {
Expand Down Expand Up @@ -255,7 +259,7 @@ const TradeBox = ({
setWaitingWebln(true);
setOpen({ ...open, webln: true });
webln
.makeInvoice(lightning.amount)
.makeInvoice(() => lightning.amount)
.then((invoice: any) => {
if (invoice) {
updateInvoice(invoice.paymentRequest);
Expand All @@ -281,7 +285,7 @@ const TradeBox = ({
}, [order.status]);

const statusToContract = function (order: Order) {
const status = 6;
const status = order.status;
const isBuyer = order.is_buyer;
const isMaker = order.is_maker;

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/models/Order.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface TradeCoordinatorSummary {
contract_timestamp: Date;
contract_total_time: number;
contract_exchange_rate: number;
routing_fee_sats: number;
routing_budget_sats: number;
trade_revenue_sats: number;
}

Expand Down

0 comments on commit d002098

Please sign in to comment.