mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Add stealth invoices (#210)
* Add stealth invoice switch to profile * Add stealth invoice including only order uuid * Add explanatory tooltip for stealth invoices and fix code smells
This commit is contained in:
parent
6090e21f6a
commit
eff58dc91d
@ -1043,6 +1043,9 @@ class Logics:
|
|||||||
order.last_satoshis_time = timezone.now()
|
order.last_satoshis_time = timezone.now()
|
||||||
bond_satoshis = int(order.last_satoshis * order.bond_size/100)
|
bond_satoshis = int(order.last_satoshis * order.bond_size/100)
|
||||||
|
|
||||||
|
if user.profile.wants_stealth:
|
||||||
|
description = f"This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally. Payment reference: {order.reference}"
|
||||||
|
else:
|
||||||
description = f"RoboSats - Publishing '{str(order)}' - Maker bond - This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally."
|
description = f"RoboSats - Publishing '{str(order)}' - Maker bond - This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally."
|
||||||
|
|
||||||
# Gen hold Invoice
|
# Gen hold Invoice
|
||||||
@ -1155,6 +1158,9 @@ class Logics:
|
|||||||
order.last_satoshis_time = timezone.now()
|
order.last_satoshis_time = timezone.now()
|
||||||
bond_satoshis = int(order.last_satoshis * order.bond_size/100)
|
bond_satoshis = int(order.last_satoshis * order.bond_size/100)
|
||||||
pos_text = "Buying" if cls.is_buyer(order, user) else "Selling"
|
pos_text = "Buying" if cls.is_buyer(order, user) else "Selling"
|
||||||
|
if user.profile.wants_stealth:
|
||||||
|
description = f"This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally. Payment reference: {order.reference}"
|
||||||
|
else:
|
||||||
description = (
|
description = (
|
||||||
f"RoboSats - Taking 'Order {order.id}' {pos_text} BTC for {str(float(order.amount)) + Currency.currency_dict[str(order.currency.currency)]}"
|
f"RoboSats - Taking 'Order {order.id}' {pos_text} BTC for {str(float(order.amount)) + Currency.currency_dict[str(order.currency.currency)]}"
|
||||||
+
|
+
|
||||||
@ -1247,6 +1253,9 @@ class Logics:
|
|||||||
|
|
||||||
# If there was no taker_bond object yet, generate one
|
# If there was no taker_bond object yet, generate one
|
||||||
escrow_satoshis = cls.escrow_amount(order, user)[1]["escrow_amount"] # Amount was fixed when taker bond was locked, fee applied here
|
escrow_satoshis = cls.escrow_amount(order, user)[1]["escrow_amount"] # Amount was fixed when taker bond was locked, fee applied here
|
||||||
|
if user.profile.wants_stealth:
|
||||||
|
description = f"This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally. Payment reference: {order.reference}"
|
||||||
|
else:
|
||||||
description = f"RoboSats - Escrow amount for '{str(order)}' - It WILL FREEZE IN YOUR WALLET. It will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment."
|
description = f"RoboSats - Escrow amount for '{str(order)}' - It WILL FREEZE IN YOUR WALLET. It will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment."
|
||||||
|
|
||||||
# Gen hold Invoice
|
# Gen hold Invoice
|
||||||
|
@ -297,6 +297,7 @@ class Order(models.Model):
|
|||||||
NESINV = 4, "Neither escrow locked or invoice submitted"
|
NESINV = 4, "Neither escrow locked or invoice submitted"
|
||||||
|
|
||||||
# order info
|
# order info
|
||||||
|
reference = models.UUIDField(default = uuid.uuid4, editable = False)
|
||||||
status = models.PositiveSmallIntegerField(choices=Status.choices,
|
status = models.PositiveSmallIntegerField(choices=Status.choices,
|
||||||
null=False,
|
null=False,
|
||||||
default=Status.WFB)
|
default=Status.WFB)
|
||||||
@ -652,6 +653,10 @@ class Profile(models.Model):
|
|||||||
default=None,
|
default=None,
|
||||||
blank=True)
|
blank=True)
|
||||||
|
|
||||||
|
# Stealth invoices
|
||||||
|
wants_stealth = models.BooleanField(default=False,
|
||||||
|
null=False)
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def create_user_profile(sender, instance, created, **kwargs):
|
def create_user_profile(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
|
@ -147,3 +147,6 @@ class TickSerializer(serializers.ModelSerializer):
|
|||||||
"fee",
|
"fee",
|
||||||
)
|
)
|
||||||
depth = 1
|
depth = 1
|
||||||
|
|
||||||
|
class StealthSerializer(serializers.Serializer):
|
||||||
|
wantsStealth = serializers.BooleanField()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import MakerView, OrderView, UserView, BookView, InfoView, RewardView, PriceView, LimitView, HistoricalView, TickView
|
from .views import MakerView, OrderView, UserView, BookView, InfoView, RewardView, PriceView, LimitView, HistoricalView, TickView, StealthView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("make/", MakerView.as_view()),
|
path("make/", MakerView.as_view()),
|
||||||
@ -16,4 +16,5 @@ urlpatterns = [
|
|||||||
path("reward/", RewardView.as_view()),
|
path("reward/", RewardView.as_view()),
|
||||||
path("historical/", HistoricalView.as_view()),
|
path("historical/", HistoricalView.as_view()),
|
||||||
path("ticks/", TickView.as_view()),
|
path("ticks/", TickView.as_view()),
|
||||||
|
path("stealth/", StealthView.as_view()),
|
||||||
]
|
]
|
||||||
|
34
api/views.py
34
api/views.py
@ -2,7 +2,7 @@ import os
|
|||||||
from re import T
|
from re import T
|
||||||
from django.db.models import Sum, Q
|
from django.db.models import Sum, Q
|
||||||
from rest_framework import status, viewsets
|
from rest_framework import status, viewsets
|
||||||
from rest_framework.generics import CreateAPIView, ListAPIView
|
from rest_framework.generics import CreateAPIView, ListAPIView, UpdateAPIView
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
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, TickSerializer
|
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer, TickSerializer, StealthSerializer
|
||||||
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Currency, Profile
|
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Currency, Profile
|
||||||
from control.models import AccountingDay, BalanceLog
|
from control.models import AccountingDay, BalanceLog
|
||||||
from api.logics import Logics
|
from api.logics import Logics
|
||||||
@ -740,10 +740,13 @@ class UserView(APIView):
|
|||||||
user.profile.is_referred = True
|
user.profile.is_referred = True
|
||||||
user.profile.referred_by = queryset[0]
|
user.profile.referred_by = queryset[0]
|
||||||
|
|
||||||
|
user.profile.wants_stealth = False
|
||||||
|
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
|
||||||
context["public_key"] = user.profile.public_key
|
context["public_key"] = user.profile.public_key
|
||||||
context["encrypted_private_key"] = user.profile.encrypted_private_key
|
context["encrypted_private_key"] = user.profile.encrypted_private_key
|
||||||
|
context["wants_stealth"] = user.profile.wants_stealth
|
||||||
return Response(context, status=status.HTTP_201_CREATED)
|
return Response(context, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
# log in user and return pub/priv keys if existing
|
# log in user and return pub/priv keys if existing
|
||||||
@ -755,6 +758,7 @@ class UserView(APIView):
|
|||||||
context["encrypted_private_key"] = user.profile.encrypted_private_key
|
context["encrypted_private_key"] = user.profile.encrypted_private_key
|
||||||
context["earned_rewards"] = user.profile.earned_rewards
|
context["earned_rewards"] = user.profile.earned_rewards
|
||||||
context["referral_code"] = str(user.profile.referral_code)
|
context["referral_code"] = str(user.profile.referral_code)
|
||||||
|
context["wants_stealth"] = user.profile.wants_stealth
|
||||||
|
|
||||||
# return active order or last made order if any
|
# return active order or last made order if any
|
||||||
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(request.user)
|
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(request.user)
|
||||||
@ -1026,3 +1030,29 @@ class HistoricalView(ListAPIView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Response(payload, status.HTTP_200_OK)
|
return Response(payload, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
class StealthView(UpdateAPIView):
|
||||||
|
|
||||||
|
serializer_class = StealthSerializer
|
||||||
|
|
||||||
|
def put(self, request):
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"bad_request":
|
||||||
|
"Woops! It seems you do not have a robot avatar"
|
||||||
|
},
|
||||||
|
status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not serializer.is_valid():
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
stealth = serializer.data.get("wantsStealth")
|
||||||
|
|
||||||
|
request.user.profile.wants_stealth = stealth
|
||||||
|
request.user.profile.save()
|
||||||
|
|
||||||
|
return Response({"wantsStealth": stealth})
|
||||||
|
@ -124,6 +124,17 @@ class BottomBar extends Component {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSetStealthInvoice = (wantsStealth) => {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')},
|
||||||
|
body: JSON.stringify({wantsStealth: wantsStealth}),
|
||||||
|
};
|
||||||
|
fetch('/api/stealth/', requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => this.props.setAppState({stealthInvoices: data.wantsStealth}));
|
||||||
|
}
|
||||||
|
|
||||||
getHost(){
|
getHost(){
|
||||||
var url = (window.location != window.parent.location) ? this.getHost(document.referrer) : document.location.href;
|
var url = (window.location != window.parent.location) ? this.getHost(document.referrer) : document.location.href;
|
||||||
return url.split('/')[2]
|
return url.split('/')[2]
|
||||||
@ -488,6 +499,8 @@ bottomBarPhone =()=>{
|
|||||||
badInvoice={this.state.badInvoice}
|
badInvoice={this.state.badInvoice}
|
||||||
earnedRewards={this.props.earnedRewards}
|
earnedRewards={this.props.earnedRewards}
|
||||||
setAppState={this.props.setAppState}
|
setAppState={this.props.setAppState}
|
||||||
|
stealthInvoices={this.props.stealthInvoices}
|
||||||
|
handleSetStealthInvoice={this.handleSetStealthInvoice}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatsDialog
|
<StatsDialog
|
||||||
|
@ -48,6 +48,8 @@ type Props = {
|
|||||||
withdrawn: boolean;
|
withdrawn: boolean;
|
||||||
badInvoice: boolean | string;
|
badInvoice: boolean | string;
|
||||||
earnedRewards: number;
|
earnedRewards: number;
|
||||||
|
stealthInvoices: boolean;
|
||||||
|
handleSetStealthInvoice: (stealth: boolean) => void;
|
||||||
setAppState: (state: any) => void; // TODO: move to a ContextProvider
|
setAppState: (state: any) => void; // TODO: move to a ContextProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +67,8 @@ const ProfileDialog = ({
|
|||||||
badInvoice,
|
badInvoice,
|
||||||
earnedRewards,
|
earnedRewards,
|
||||||
setAppState,
|
setAppState,
|
||||||
|
stealthInvoices,
|
||||||
|
handleSetStealthInvoice,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -206,6 +210,24 @@ const ProfileDialog = ({
|
|||||||
|
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
|
||||||
|
<Grid container>
|
||||||
|
<Tooltip placement="top" enterTouchDelay={0} title={t("stealth_invoice_explaination")}>
|
||||||
|
<Grid item>
|
||||||
|
<FormControlLabel
|
||||||
|
labelPlacement="start"
|
||||||
|
label={`${t("Use stealth invoices")}`}
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={stealthInvoices}
|
||||||
|
onChange={() => handleSetStealthInvoice(!stealthInvoices)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
@ -86,6 +86,7 @@ class UserGenPage extends Component {
|
|||||||
bad_request: data.bad_request,
|
bad_request: data.bad_request,
|
||||||
found: data.found,
|
found: data.found,
|
||||||
loadingRobot:false,
|
loadingRobot:false,
|
||||||
|
stealthInvoices: data.wants_stealth,
|
||||||
})
|
})
|
||||||
&
|
&
|
||||||
// Add nick and token to App state (token only if not a bad request)
|
// Add nick and token to App state (token only if not a bad request)
|
||||||
@ -96,6 +97,7 @@ class UserGenPage extends Component {
|
|||||||
referralCode: data.referral_code,
|
referralCode: data.referral_code,
|
||||||
earnedRewards: data.earned_rewards,
|
earnedRewards: data.earned_rewards,
|
||||||
lastOrderId: data.last_order_id ? data.last_order_id : null,
|
lastOrderId: data.last_order_id ? data.last_order_id : null,
|
||||||
|
stealthInvoices: data.wants_stealth,
|
||||||
})
|
})
|
||||||
:
|
:
|
||||||
(this.props.setAppState({
|
(this.props.setAppState({
|
||||||
@ -106,6 +108,7 @@ class UserGenPage extends Component {
|
|||||||
lastOrderId: data.last_order_id ? data.last_order_id : null,
|
lastOrderId: data.last_order_id ? data.last_order_id : null,
|
||||||
referralCode: data.referral_code,
|
referralCode: data.referral_code,
|
||||||
earnedRewards: data.earned_rewards,
|
earnedRewards: data.earned_rewards,
|
||||||
|
stealthInvoices: data.wants_stealth,
|
||||||
})) & writeCookie("robot_token",token)
|
})) & writeCookie("robot_token",token)
|
||||||
& writeCookie("pub_key",data.public_key.split('\n').join('\\'))
|
& writeCookie("pub_key",data.public_key.split('\n').join('\\'))
|
||||||
& writeCookie("enc_priv_key",data.encrypted_private_key.split('\n').join('\\')))
|
& writeCookie("enc_priv_key",data.encrypted_private_key.split('\n').join('\\')))
|
||||||
|
@ -227,6 +227,8 @@
|
|||||||
"Join RoboSats' Subreddit":"Join RoboSats' Subreddit",
|
"Join RoboSats' Subreddit":"Join RoboSats' Subreddit",
|
||||||
"RoboSats in Reddit":"RoboSats in Reddit",
|
"RoboSats in Reddit":"RoboSats in Reddit",
|
||||||
"Current onchain payout fee":"Current onchain payout fee",
|
"Current onchain payout fee":"Current onchain payout fee",
|
||||||
|
"Use stealth invoices":"Use stealth invoices",
|
||||||
|
"stealth_invoice_explaination":"Stealth invoices do not contain details about the trade except a payment reference. Enable this setting if you don't want to disclose details to a custodial lightning wallet.",
|
||||||
|
|
||||||
"ORDER PAGE - OrderPage.js": "Order details page",
|
"ORDER PAGE - OrderPage.js": "Order details page",
|
||||||
"Order Box":"Order Box",
|
"Order Box":"Order Box",
|
||||||
|
Loading…
Reference in New Issue
Block a user