Add maker selects public duration

This commit is contained in:
Reckless_Satoshi 2022-03-18 14:21:13 -07:00
parent 967c441bb4
commit a36f23b572
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
15 changed files with 159 additions and 94 deletions

View File

@ -82,7 +82,7 @@ EXP_TAKER_BOND_INVOICE = 200
# Time a order is public in the book HOURS
DEFAULT_PUBLIC_ORDER_DURATION = 24
MAX_PUBLIC_ORDER_DURATION = 24
MIN_PUBLIC_ORDER_DURATION = 0.25
MIN_PUBLIC_ORDER_DURATION = 0.166
# Time to provide a valid invoice and the trade escrow MINUTES
INVOICE_AND_ESCROW_DURATION = 30

View File

@ -13,21 +13,25 @@ class ProfileInline(admin.StackedInline):
can_delete = False
fields = ("avatar_tag", )
readonly_fields = ["avatar_tag"]
show_change_link = True
# extended users with avatars
@admin.register(User)
class EUserAdmin(UserAdmin):
class EUserAdmin(AdminChangeLinksMixin, UserAdmin):
inlines = [ProfileInline]
list_display = (
"avatar_tag",
"id",
"profile_link",
"username",
"last_login",
"date_joined",
"is_staff",
)
list_display_links = ("id", "username")
change_links = (
"profile",
)
ordering = ("-id", )
def avatar_tag(self, obj):
@ -95,7 +99,7 @@ class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
)
list_filter = ("type", "concept", "status")
ordering = ("-expires_at", )
search_fields = ["payment_hash","num_satoshis"]
search_fields = ["payment_hash","num_satoshis","sender__username","receiver__username","description"]
@admin.register(Profile)
@ -119,6 +123,7 @@ class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display_links = ("avatar_tag", "id")
change_links = ["user"]
readonly_fields = ["avatar_tag"]
search_fields = ["user__username","id"]
@admin.register(Currency)

View File

@ -27,7 +27,6 @@ EXP_TAKER_BOND_INVOICE = int(config("EXP_TAKER_BOND_INVOICE"))
BOND_EXPIRY = int(config("BOND_EXPIRY"))
ESCROW_EXPIRY = int(config("ESCROW_EXPIRY"))
PUBLIC_ORDER_DURATION = int(config("PUBLIC_ORDER_DURATION"))
INVOICE_AND_ESCROW_DURATION = int(config("INVOICE_AND_ESCROW_DURATION"))
FIAT_EXCHANGE_DURATION = int(config("FIAT_EXCHANGE_DURATION"))
@ -129,7 +128,7 @@ class Logics:
order.taker = user
order.status = Order.Status.TAK
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.TAK])
seconds=order.t_to_expire(Order.Status.TAK))
order.save()
# send_message.delay(order.id,'order_taken') # Too spammy
return True, None
@ -336,7 +335,7 @@ class Logics:
order.is_disputed = True
order.status = Order.Status.DIS
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.DIS])
seconds=order.t_to_expire(Order.Status.DIS))
order.save()
# User could be None if a dispute is open automatically due to weird expiration.
@ -380,7 +379,7 @@ class Logics:
if order.maker_statement not in [None,""] and order.taker_statement not in [None,""]:
order.status = Order.Status.WFR
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.WFR])
seconds=order.t_to_expire(Order.Status.WFR))
order.save()
return True, None
@ -472,7 +471,7 @@ class Logics:
if order.status == Order.Status.WFI:
order.status = Order.Status.CHA
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.CHA])
seconds=order.t_to_expire(Order.Status.CHA))
# If the order status is 'Waiting for both'. Move forward to 'waiting for escrow'
if order.status == Order.Status.WF2:
@ -483,7 +482,7 @@ class Logics:
elif order.trade_escrow.status == LNPayment.Status.LOCKED:
order.status = Order.Status.CHA
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.CHA])
seconds=order.t_to_expire(Order.Status.CHA))
else:
order.status = Order.Status.WFE
@ -661,7 +660,7 @@ class Logics:
def publish_order(order):
order.status = Order.Status.PUB
order.expires_at = order.created_at + timedelta(
seconds=Order.t_to_expire[Order.Status.PUB])
seconds=order.t_to_expire(Order.Status.PUB))
order.save()
# send_message.delay(order.id,'order_published') # too spammy
return
@ -708,7 +707,7 @@ class Logics:
hold_payment = LNNode.gen_hold_invoice(
bond_satoshis,
description,
invoice_expiry=Order.t_to_expire[Order.Status.WFB],
invoice_expiry=order.t_to_expire(Order.Status.WFB),
cltv_expiry_secs=BOND_EXPIRY * 3600,
)
except Exception as e:
@ -759,7 +758,7 @@ class Logics:
# With the bond confirmation the order is extended 'public_order_duration' hours
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.WF2])
seconds=order.t_to_expire(Order.Status.WF2))
order.status = Order.Status.WF2
order.save()
@ -822,7 +821,7 @@ class Logics:
hold_payment = LNNode.gen_hold_invoice(
bond_satoshis,
description,
invoice_expiry=Order.t_to_expire[Order.Status.TAK],
invoice_expiry=order.t_to_expire(Order.Status.TAK),
cltv_expiry_secs=BOND_EXPIRY * 3600,
)
@ -850,7 +849,7 @@ class Logics:
)
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.TAK])
seconds=order.t_to_expire(Order.Status.TAK))
order.save()
return True, {
"bond_invoice": hold_payment["invoice"],
@ -866,7 +865,7 @@ class Logics:
elif order.status == Order.Status.WFE:
order.status = Order.Status.CHA
order.expires_at = timezone.now() + timedelta(
seconds=Order.t_to_expire[Order.Status.CHA])
seconds=order.t_to_expire(Order.Status.CHA))
order.save()
@classmethod
@ -909,7 +908,7 @@ class Logics:
hold_payment = LNNode.gen_hold_invoice(
escrow_satoshis,
description,
invoice_expiry=Order.t_to_expire[Order.Status.WF2],
invoice_expiry=order.t_to_expire(Order.Status.WF2),
cltv_expiry_secs=ESCROW_EXPIRY * 3600,
)

View File

@ -45,11 +45,17 @@ class Command(BaseCommand):
except:
print(f'No profile with token {token}')
continue
profile.telegram_chat_id = result['message']['from']['id']
profile.telegram_lang_code = result['message']['from']['language_code']
self.telegram.welcome(profile.user)
profile.telegram_enabled = True
profile.save()
attempts = 5
while attempts >= 0:
try:
profile.telegram_chat_id = result['message']['from']['id']
profile.telegram_lang_code = result['message']['from']['language_code']
self.telegram.welcome(profile.user)
profile.telegram_enabled = True
profile.save()
except:
attempts = attempts - 1
offset = response['result'][-1]['update_id']

View File

@ -5,6 +5,7 @@ from django.core.validators import (
MinValueValidator,
validate_comma_separated_integer_list,
)
from django.utils import timezone
from django.db.models.signals import post_save, pre_delete
from django.template.defaultfilters import truncatechars
from django.dispatch import receiver
@ -38,7 +39,7 @@ class Currency(models.Model):
null=True,
validators=[MinValueValidator(0)],
)
timestamp = models.DateTimeField(auto_now_add=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
# returns currency label ( 3 letters code)
@ -181,7 +182,7 @@ class Order(models.Model):
status = models.PositiveSmallIntegerField(choices=Status.choices,
null=False,
default=Status.WFB)
created_at = models.DateTimeField(auto_now_add=True)
created_at = models.DateTimeField(default=timezone.now)
expires_at = models.DateTimeField()
# order details
@ -218,6 +219,17 @@ class Order(models.Model):
],
blank=True,
)
# optionally makers can choose the public order duration length (seconds)
public_duration = models.PositiveBigIntegerField(
default=60*60*int(config("DEFAULT_PUBLIC_ORDER_DURATION"))-1,
null=False,
validators=[
MinValueValidator(60*60*float(config("MIN_PUBLIC_ORDER_DURATION"))), # Min is 10 minutes
MaxValueValidator(60*60*float(config("MAX_PUBLIC_ORDER_DURATION"))), # Max is 24 Hours
],
blank=False,
)
# how many sats at creation and at last check (relevant for marked to market)
t0_satoshis = models.PositiveBigIntegerField(
null=True,
@ -311,31 +323,35 @@ class Order(models.Model):
maker_platform_rated = models.BooleanField(default=False, null=False)
taker_platform_rated = models.BooleanField(default=False, null=False)
t_to_expire = {
0: int(config("EXP_MAKER_BOND_INVOICE")), # 'Waiting for maker bond'
1: 60 * 60 * int(config("PUBLIC_ORDER_DURATION")), # 'Public'
2: 0, # 'Deleted'
3: int(config("EXP_TAKER_BOND_INVOICE")), # 'Waiting for taker bond'
4: 0, # 'Cancelled'
5: 0, # 'Expired'
6: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting for trade collateral and buyer invoice'
7: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting only for seller trade collateral'
8: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting only for buyer invoice'
9: 60 * 60 * int(config("FIAT_EXCHANGE_DURATION")), # 'Sending fiat - In chatroom'
10: 60 * 60 * int(config("FIAT_EXCHANGE_DURATION")),# 'Fiat sent - In chatroom'
11: 1 * 24 * 60 * 60, # 'In dispute'
12: 0, # 'Collaboratively cancelled'
13: 24 * 60 * 60, # 'Sending satoshis to buyer'
14: 24 * 60 * 60, # 'Sucessful trade'
15: 24 * 60 * 60, # 'Failed lightning network routing'
16: 10 * 24 * 60 * 60, # 'Wait for dispute resolution'
17: 24 * 60 * 60, # 'Maker lost dispute'
18: 24 * 60 * 60, # 'Taker lost dispute'
}
def __str__(self):
return f"Order {self.id}: {self.Types(self.type).label} BTC for {float(self.amount)} {self.currency}"
def t_to_expire(self, status):
t_to_expire = {
0: int(config("EXP_MAKER_BOND_INVOICE")), # 'Waiting for maker bond'
1: self.public_duration, # 'Public'
2: 0, # 'Deleted'
3: int(config("EXP_TAKER_BOND_INVOICE")), # 'Waiting for taker bond'
4: 0, # 'Cancelled'
5: 0, # 'Expired'
6: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting for trade collateral and buyer invoice'
7: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting only for seller trade collateral'
8: 60 * int(config("INVOICE_AND_ESCROW_DURATION")), # 'Waiting only for buyer invoice'
9: 60 * 60 * int(config("FIAT_EXCHANGE_DURATION")), # 'Sending fiat - In chatroom'
10: 60 * 60 * int(config("FIAT_EXCHANGE_DURATION")),# 'Fiat sent - In chatroom'
11: 1 * 24 * 60 * 60, # 'In dispute'
12: 0, # 'Collaboratively cancelled'
13: 24 * 60 * 60, # 'Sending satoshis to buyer'
14: 24 * 60 * 60, # 'Sucessful trade'
15: 24 * 60 * 60, # 'Failed lightning network routing'
16: 10 * 24 * 60 * 60, # 'Wait for dispute resolution'
17: 24 * 60 * 60, # 'Maker lost dispute'
18: 24 * 60 * 60, # 'Taker lost dispute'
}
return t_to_expire[status]
@receiver(pre_delete, sender=Order)
def delete_lnpayment_at_order_deletion(sender, instance, **kwargs):
@ -393,7 +409,7 @@ class Profile(models.Model):
null=False
)
telegram_lang_code = models.CharField(
max_length=4,
max_length=10,
null=True,
blank=True
)
@ -529,7 +545,7 @@ class MarketTick(models.Model):
currency = models.ForeignKey(Currency,
null=True,
on_delete=models.SET_NULL)
timestamp = models.DateTimeField(auto_now_add=True)
timestamp = models.DateTimeField(default=timezone.now)
# Relevant to keep record of the historical fee, so the insight on the premium can be better analyzed
fee = models.DecimalField(

View File

@ -35,9 +35,9 @@ class MakeOrderSerializer(serializers.ModelSerializer):
"is_explicit",
"premium",
"satoshis",
"public_duration",
)
class UpdateOrderSerializer(serializers.Serializer):
invoice = serializers.CharField(max_length=2000,
allow_null=True,

View File

@ -191,6 +191,9 @@ def send_message(order_id, message):
from api.messages import Telegram
telegram = Telegram()
if message == 'welcome':
telegram.welcome(order)
if message == 'order_taken':
telegram.order_taken(order)

View File

@ -72,6 +72,7 @@ class MakerView(CreateAPIView):
premium = serializer.data.get("premium")
satoshis = serializer.data.get("satoshis")
is_explicit = serializer.data.get("is_explicit")
public_duration = serializer.data.get("public_duration")
valid, context, _ = Logics.validate_already_maker_or_taker(
request.user)
@ -90,6 +91,7 @@ class MakerView(CreateAPIView):
expires_at=timezone.now() + timedelta(
seconds=EXP_MAKER_BOND_INVOICE), # TODO Move to class method
maker=request.user,
public_duration=public_duration,
)
# TODO move to Order class method when new instance is created!
@ -155,7 +157,7 @@ class OrderView(viewsets.ViewSet):
)
data = ListOrderSerializer(order).data
data["total_secs_exp"] = Order.t_to_expire[order.status]
data["total_secs_exp"] = order.t_to_expire(order.status)
# if user is under a limit (penalty), inform him.
is_penalized, time_out = Logics.is_penalized(request.user)

View File

@ -33,7 +33,7 @@ export default class App extends Component {
render() {
return (
<ThemeProvider theme={this.state.dark ? this.darkTheme : this.lightTheme}>
<UnsafeAlert/>
<UnsafeAlert className="unsafeAlert"/>
<HomePage setAppState={this.setAppState}/>
</ThemeProvider>
);

View File

@ -6,6 +6,7 @@ import { Link } from 'react-router-dom'
import getFlags from './getFlags'
import LockIcon from '@mui/icons-material/Lock';
import SelfImprovementIcon from '@mui/icons-material/SelfImprovement';
function getCookie(name) {
let cookieValue = null;
@ -57,7 +58,8 @@ export default class MakerPage extends Component {
currencies_dict: {"1":"USD"},
showAdvanced: false,
allowBondless: false,
publicExpiryTime: Date.now() + 86400000,
publicExpiryTime: new Date(0, 0, 0, 23, 59),
publicDuration: 23*60*60 + 59*60,
enableAmountRange: false,
minAmount: null,
bondSize: 1,
@ -149,6 +151,7 @@ export default class MakerPage extends Component {
is_explicit: this.state.is_explicit,
premium: this.state.is_explicit ? null: this.state.premium,
satoshis: this.state.is_explicit ? this.state.satoshis: null,
public_duration: this.state.publicDuration,
}),
};
fetch("/api/make/",requestOptions)
@ -335,31 +338,65 @@ export default class MakerPage extends Component {
)
}
handleChangePublicDuration = (date) => {
console.log(date)
let d = new Date(date),
hours = d.getHours(),
minutes = d.getMinutes();
var total_secs = hours*60*60 + minutes * 60;
this.setState({
changedPublicExpiryTime: true,
publicExpiryTime: date,
publicDuration: total_secs,
badDuration: false,
});
}
AdvancedMakerOptions = () => {
return(
<Paper elevation={12} style={{ padding: 8, width:280, align:'center'}}>
<Grid container xs={12} spacing={1}>
<Grid item xs={12} align="center" spacing={1}>
<br/>
<LocalizationProvider dateAdapter={DateFnsUtils}>
<TimePicker
ampm={false}
openTo="hours"
views={['hours', 'minutes']}
inputFormat="HH:mm"
mask="__:__"
renderInput={(props) => <TextField {...props} />}
label="Public Duration (HH:mm)"
value={this.state.publicExpiryTime}
onChange={this.handleChangePublicDuration}
minTime={new Date(0, 0, 0, 0, 10)}
maxTime={new Date(0, 0, 0, 23, 59)}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<FormControl align="center">
<FormHelperText>
<Tooltip enterTouchDelay="0" title={"Let the taker chose an amount within the range"}>
<Tooltip enterTouchDelay="0" title={"COMING SOON - Let the taker chose an amount within the range"}>
<div align="center">
Amount Range
</div>
</Tooltip>
</FormHelperText>
<Grid container xs={12} align="center">
<Grid item xs={2} align="center">
<Grid container xs={12} align="left">
<Grid item xs={3} align="left">
<Checkbox
disabled
//disabled={this.state.amount == null}
onChange={()=>this.setState({enableAmountRange:!this.state.enableAmountRange})}/>
disabled
//disabled={this.state.amount == null}
onChange={()=>this.setState({enableAmountRange:!this.state.enableAmountRange})}/>
</Grid>
<Grid xs={1}/>
<Grid item xs={9} align="center">
<Grid item xs={9} align="left">
<Slider
sx={{width:170, align:"center"}}
sx={{width:140, align:"center"}}
disabled={!this.state.enableAmountRange}
aria-label="Amount Range"
defaultValue={this.state.amount}
@ -372,7 +409,7 @@ export default class MakerPage extends Component {
marks={this.state.amount == null ?
null
:
[{value: this.state.amount*this.minAmountFraction,label: pn(this.state.amount*this.minAmountFraction)+" "+ this.state.currencyCode},
[{value: this.state.amount*this.minAmountFraction,label: parseFloat(parseFloat(this.state.amount*this.minAmountFraction).toFixed(4))+" "+ this.state.currencyCode},
{value: this.state.amount,label: this.state.amount+" "+this.state.currencyCode}]}
min={this.state.amount*this.minAmountFraction}
max={this.state.amount}
@ -382,23 +419,11 @@ export default class MakerPage extends Component {
</Grid>
</FormControl>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<LocalizationProvider dateAdapter={DateFnsUtils}>
<TimePicker
disabled
renderInput={(props) => <TextField {...props} />}
label="Public Order Expiry Time"
value={this.state.publicExpiryTime}
onChange={(newValue) => {this.setState({publicExpiryTime: newValue})}}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<FormControl align="center">
<FormHelperText>
<Tooltip enterTouchDelay="0" title={"Increase for a higher safety assurance"}>
<Tooltip enterTouchDelay="0" title={"COMING SOON - Increase for a higher safety assurance"}>
<div align="center" style={{display:'flex',flexWrap:'wrap', transform: 'translate(20%, 0)'}}>
<LockIcon sx={{height:20,width:20}}/> Fidelity Bond Size
</div>
@ -425,7 +450,7 @@ export default class MakerPage extends Component {
</Grid>
<Grid item xs={12} align="center" spacing={1}>
<Tooltip enterTouchDelay="0" title={"High risk! Limited to "+ this.maxBondlessSats/1000 +"K Sats"}>
<Tooltip enterTouchDelay="0" title={"COMING SOON - High risk! Limited to "+ this.maxBondlessSats/1000 +"K Sats"}>
<FormControlLabel
label={<a>Allow bondless taker (<a href="https://git.robosats.com" target="_blank">info</a>)</a>}
control={
@ -454,16 +479,18 @@ export default class MakerPage extends Component {
</Grid> */}
<Grid item xs={12} align="center">
<div className="advancedSwitch">
<Tooltip enterTouchDelay="0" title="Coming soon">
{/* <Tooltip enterTouchDelay="0" title="Coming soon"> */}
<FormControlLabel
size="small"
disableTypography={true}
label={<Typography variant="body2">Advanced</Typography>}
labelPlacement="start" control={
<Switch
//disabled
size="small"
checked={this.state.showAdvanced}
onChange={()=> this.setState({showAdvanced: !this.state.showAdvanced})}/>}
label="Advanced"
/>
</Tooltip>
{/* </Tooltip> */}
</div>
</Grid>

View File

@ -25,9 +25,9 @@ export default class UnsafeAlert extends Component {
render() {
return (
(!this.safe_urls.includes(this.getHost()) & this.state.show) ?
<>
<div>
<MediaQuery minWidth={800}>
<Alert severity="warning"
<Alert severity="warning" sx={{maxHeight:100, zIndex:9999}} z-index={9999}
action={<Button onClick={() => this.setState({show:false})}>Hide</Button>}
>
<AlertTitle>You are not using RoboSats privately</AlertTitle>
@ -37,7 +37,7 @@ export default class UnsafeAlert extends Component {
</MediaQuery>
<MediaQuery maxWidth={799}>
<Alert severity="warning" >
<Alert severity="warning" sx={{maxHeight:100, zIndex:9999}} z-index={9999}>
<AlertTitle>You are not using RoboSats privately</AlertTitle>
You will not be able to complete a
trade. Use <a href='https://www.torproject.org/download/' target="_blank">Tor Browser</a> and visit the <a href='http://robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion' target="_blank">Onion</a> site.
@ -48,7 +48,7 @@ export default class UnsafeAlert extends Component {
</div>
</Alert>
</MediaQuery>
</>
</div>
:
null
)

View File

@ -151,7 +151,7 @@ export default class UserGenPage extends Component {
return (
<Grid container spacing={1}>
<Grid item>
<div style={{height:40}}/>
<div className='clickTrough'/>
</Grid>
<Grid item xs={12} align="center" sx={{width:370, height:260}}>
{!this.state.loadingRobot ?
@ -250,7 +250,7 @@ export default class UserGenPage extends Component {
<Grid item xs={12} align="center" spacing={2} sx={{width:370}}>
<Grid item>
<div style={{height:30}}/>
<div style={{height:40}}/>
</Grid>
<div style={{width:370, left:30}}>
<Grid container xs={12} align="center">
@ -266,7 +266,6 @@ export default class UserGenPage extends Component {
</Grid>
</div>
</Grid>
</Grid>
);
}

View File

@ -18,15 +18,18 @@ body {
height: 100%;
}
.amboss{
fill:url(#SVGID_1_);
}
.appCenter {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%) translate(0,-20px);
z-index: 1;
}
.clickTrough{
height: 50px;
pointer-events: none;
z-index: 1;
}
.bottomBar {
@ -36,10 +39,14 @@ body {
height: 40px;
}
.amboss{
fill:url(#SVGID_1_);
}
.advancedSwitch{
width: 240;
width: 20;
left: 50%;
transform: translate(55px, 0px);
transform: translate(62px, 0px);
margin-right: 0;
margin-left: auto;
}

File diff suppressed because one or more lines are too long

View File

@ -91,6 +91,7 @@ TEMPLATES = [
]
WSGI_APPLICATION = "robosats.wsgi.application"
USE_TZ = True
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases