From 7218b9b0d39ee567c13b0ecbc3bf2f0e899cc212 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Wed, 19 Jan 2022 11:37:10 -0800 Subject: [PATCH 01/42] Logics update: escrow settles exactly at buyer payout time. --- api/logics.py | 16 +++++------ api/management/commands/clean_orders.py | 15 +++++++++-- api/management/commands/follow_invoices.py | 3 +++ api/tasks.py | 2 +- frontend/src/components/InfoDialog.js | 31 ++++++++++++---------- frontend/src/components/TradeBox.js | 4 +-- robosats/celery/__init__.py | 4 +-- 7 files changed, 46 insertions(+), 29 deletions(-) diff --git a/api/logics.py b/api/logics.py index 7059ea10..e3fd6f55 100644 --- a/api/logics.py +++ b/api/logics.py @@ -514,8 +514,7 @@ class Logics(): # Do not gen and kick out the taker if order is older than expiry time if order.expires_at < timezone.now(): - cls.cancel_bond(order.taker_bond) - cls.kick_taker(order) + cls.order_expires(order) return False, {'bad_request':'Invoice expired. You did not confirm taking the order in time.'} # Do not gen if a taker invoice exist. Do not return if it is already locked. Return the old one if still waiting. @@ -580,7 +579,7 @@ class Logics(): # Do not generate if escrow deposit time has expired if order.expires_at < timezone.now(): - cls.cancel_order(order,user) + cls.order_expires(order) return False, {'bad_request':'Invoice expired. You did not send the escrow in time.'} # Do not gen if an escrow invoice exist. Do not return if it is already locked. Return the old one if still waiting. @@ -690,12 +689,10 @@ class Logics(): # If buyer, settle escrow and mark fiat sent if cls.is_buyer(order, user): - if cls.settle_escrow(order): ##### !!! KEY LINE - SETTLES THE TRADE ESCROW !!! - order.trade_escrow.status = LNPayment.Status.SETLED - order.status = Order.Status.FSE - order.is_fiat_sent = True + order.status = Order.Status.FSE + order.is_fiat_sent = True - # If seller and fiat sent, pay buyer invoice + # If seller and fiat was sent, SETTLE ESCRO AND PAY BUYER INVOICE elif cls.is_seller(order, user): if not order.is_fiat_sent: return False, {'bad_request':'You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer.'} @@ -703,6 +700,9 @@ class Logics(): # Make sure the trade escrow is at least as big as the buyer invoice if order.trade_escrow.num_satoshis <= order.buyer_invoice.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 !!! + 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): diff --git a/api/management/commands/clean_orders.py b/api/management/commands/clean_orders.py index 033784c5..6e5faf81 100644 --- a/api/management/commands/clean_orders.py +++ b/api/management/commands/clean_orders.py @@ -15,6 +15,8 @@ class Command(BaseCommand): ''' Continuously checks order expiration times for 1 hour. If order has expires, it calls the logics module for expiration handling.''' + # TODO handle 'database is locked' + do_nothing = [Order.Status.DEL, Order.Status.UCA, Order.Status.EXP, Order.Status.FSE, Order.Status.DIS, Order.Status.CCA, @@ -34,8 +36,17 @@ class Command(BaseCommand): for idx, order in enumerate(queryset): context = str(order)+ " was "+ Order.Status(order.status).label - if Logics.order_expires(order): # Order send to expire here - debug['expired_orders'].append({idx:context}) + try: + if Logics.order_expires(order): # Order send to expire here + debug['expired_orders'].append({idx:context}) + + # If it cannot locate the hold invoice, make it expire anywway + except Exception as e: + if 'unable to locate invoice' in str(e): + self.stdout.write(str(e)) + order.status = Order.Status.EXP + debug['expired_orders'].append({idx:context}) + if debug['num_expired_orders'] > 0: self.stdout.write(str(timezone.now())) diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py index 52253a2a..5a9840bd 100644 --- a/api/management/commands/follow_invoices.py +++ b/api/management/commands/follow_invoices.py @@ -31,6 +31,8 @@ class Command(BaseCommand): ''' Follows and updates LNpayment objects until settled or canceled''' + # TODO handle 'database is locked' + lnd_state_to_lnpayment_status = { 0: LNPayment.Status.INVGEN, # OPEN 1: LNPayment.Status.SETLED, # SETTLED @@ -64,6 +66,7 @@ class Command(BaseCommand): # If it fails at finding the invoice it has been canceled. # On RoboSats DB we make a distinction between cancelled and returned (LND does not) if 'unable to locate invoice' in str(e): + self.stdout.write('unable to locate invoice') hold_lnpayment.status = LNPayment.Status.CANCEL # LND restarted. if 'wallet locked, unlock it' in str(e): diff --git a/api/tasks.py b/api/tasks.py index 82da436a..08bad68a 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -70,7 +70,7 @@ def follow_send_payment(lnpayment): order.status = Order.Status.FAI order.save() context = LNNode.payment_failure_context[response.failure_reason] - # Call for a retry here + # Call a retry here? return False, context if response.status == 2 : # Status 2 'SUCCEEDED' diff --git a/frontend/src/components/InfoDialog.js b/frontend/src/components/InfoDialog.js index d7e8adae..f48ac387 100644 --- a/frontend/src/components/InfoDialog.js +++ b/frontend/src/components/InfoDialog.js @@ -19,7 +19,7 @@ export default class InfoDialog extends Component { How does it work? -

AdequateAlice01 wants to sell bitcoin. She posts a sell order. +

Anonymous AdequateAlice01 wants to sell bitcoin. She posts a sell order. BafflingBob02 wants to buy bitcoin and he takes Alice's order. Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning @@ -51,8 +51,9 @@ export default class InfoDialog extends Component { Is RoboSats private? -

RoboSats will never ask you for your name, country or ID. For - best anonymity use Tor Browser and access the .onion hidden service.

+

RoboSats will never ask you for your name, country or ID. RoboSats does + not custody your funds, and doesn't care who you are. For best anonymity + use Tor Browser and access the .onion hidden service.

Your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid @@ -73,29 +74,33 @@ export default class InfoDialog extends Component { What is the trust model?

The buyer and the seller never have to trust each other. - Some trust on RoboSats staff is needed since linking - the seller's hold invoice and buyer payment is not atomic (yet). + Some trust on RoboSats is needed since linking the + seller's hold invoice and buyer payment is not atomic (yet). In addition, disputes are solved by the RoboSats staff.

-

While trust requirements are minimized, RoboSats could - run away with your satoshis. It could be argued that it is not - worth it, as it would instantly destroy RoboSats reputation. +

Trust requirements are minimized, however there is still one way + RoboSats could run away with your satoshis, by not releasing + the satoshis to the buyer. It could be argued that such move is not on + RoboSats interest as it would damage thereputation for a small payout. However, you should hesitate and only trade small quantities at a time. For large amounts use an onchain escrow service such as Bisq

You can build more trust on RoboSats by - inspecting the source code

+ inspecting the source code.

What happens if RoboSats suddenly disapears? -

Your sats will most likely return to you. Any hold invoice that is not +

Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if RoboSats goes down forever. This is true for both, locked bonds and trading escrows. However, - there is a small window between the buyer confirms FIAT SENT and the moment - the seller releases the satoshis when the funds could be lost. + there is a small window between the seller confirms FIAT RECEIVED and the moment + the buyer receives the satoshis when the funds could be permanentely lost if + RoboSats disappears. This window is about 1 second long. Make sure to have enough + inbound liquidity to avoid routing failures. If you have any problem, reach out + trough the RoboSats public channels.

@@ -116,9 +121,7 @@ export default class InfoDialog extends Component { RoboSats will definitely never ask for your robot token.

- - ) } diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index 1c25c9f1..b4c865dd 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -161,7 +161,7 @@ export default class TradeBox extends Component { size="small" defaultValue={this.props.data.bondInvoice} disabled="true" - helperText="This is a hold invoice. It will be charged only if you cancel or lose a dispute." + helperText="This is a hold invoice, it will freeze in your wallet. It will be charged only if you cancel or lose a dispute." color = "secondary" /> @@ -198,7 +198,7 @@ export default class TradeBox extends Component { size="small" defaultValue={this.props.data.escrowInvoice} disabled="true" - helperText="This is a hold invoice. It will be charged once the buyer confirms he sent the fiat." + helperText={"This is a hold invoice, it will freeze in your wallet. It will be released to the buyer once you confirm to have received the "+this.props.data.currencyCode+"."} color = "secondary" /> diff --git a/robosats/celery/__init__.py b/robosats/celery/__init__.py index a176b226..0d1999d2 100644 --- a/robosats/celery/__init__.py +++ b/robosats/celery/__init__.py @@ -31,9 +31,9 @@ app.conf.beat_scheduler = 'django_celery_beat.schedulers:DatabaseScheduler' # Configure the periodic tasks app.conf.beat_schedule = { - 'users-cleansing': { # Cleans abandoned users every hour + 'users-cleansing': { # Cleans abandoned users every 6 hours 'task': 'users_cleansing', - 'schedule': timedelta(hours=1), + 'schedule': timedelta(hours=6), }, 'cache-market-prices': { # Cache market prices every minutes for now. 'task': 'cache_external_market_prices', From 848a513bc32df58d0736f8fbb1d6ac08458162a4 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Wed, 19 Jan 2022 11:53:26 -0800 Subject: [PATCH 02/42] Add easter egg; Satoshi and Nakamoto as nouns in nick generator --- api/nick_generator/dicts/en/adjectives.py | 1 + api/nick_generator/dicts/en/nouns.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/api/nick_generator/dicts/en/adjectives.py b/api/nick_generator/dicts/en/adjectives.py index 07519d03..4b9e2216 100755 --- a/api/nick_generator/dicts/en/adjectives.py +++ b/api/nick_generator/dicts/en/adjectives.py @@ -4773,6 +4773,7 @@ adjectives = [ "Compound", "Important", "Robotic", + "Satoshi", "Alltoocommon", "Informative", "Anxious", diff --git a/api/nick_generator/dicts/en/nouns.py b/api/nick_generator/dicts/en/nouns.py index ae3243e8..b11cfaab 100755 --- a/api/nick_generator/dicts/en/nouns.py +++ b/api/nick_generator/dicts/en/nouns.py @@ -6346,6 +6346,7 @@ nouns = [ "Nair", "Nairo", "Naivete", + "Nakamoto", "Name", "Namesake", "Nanometer", @@ -12229,6 +12230,7 @@ nouns = [ "Sand", "Sandwich", "Satisfaction", + "Satoshi", "Save", "Savings", "Scale", From 7c0e3a74faa6c2e925efd23eb6016245d96620fc Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Wed, 19 Jan 2022 12:55:24 -0800 Subject: [PATCH 03/42] Fix premium percentile computation --- api/logics.py | 25 ++++++++++--------------- api/utils.py | 5 ++++- frontend/src/components/BookPage.js | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/api/logics.py b/api/logics.py index e3fd6f55..ff6b3e89 100644 --- a/api/logics.py +++ b/api/logics.py @@ -112,11 +112,10 @@ class Logics(): # Do not change order status if an order in any with # any of these status is sent to expire here do_nothing = [Order.Status.DEL, Order.Status.UCA, - Order.Status.EXP, Order.Status.FSE, + Order.Status.EXP, Order.Status.TLD, Order.Status.DIS, Order.Status.CCA, Order.Status.PAY, Order.Status.SUC, - Order.Status.FAI, Order.Status.MLD, - Order.Status.TLD] + Order.Status.FAI, Order.Status.MLD] if order.status in do_nothing: return False @@ -196,8 +195,8 @@ class Logics(): cls.publish_order(order) return True - elif order.status == Order.Status.CHA: - # Another weird case. The time to confirm 'fiat sent' expired. Yet no dispute + elif order.status in [Order.Status.CHA, Order.Status.FSE]: + # Another weird case. The time to confirm 'fiat sent or received' expired. Yet no dispute # was opened. Hint: a seller-scammer could persuade a buyer to not click "fiat # sent", we assume this is a dispute case by default. cls.open_dispute(order) @@ -674,16 +673,11 @@ class Logics(): else: raise e - def pay_buyer_invoice(order): - ''' Pay buyer invoice''' - suceeded, context = follow_send_payment(order.buyer_invoice) - return suceeded, context - @classmethod def confirm_fiat(cls, order, user): ''' If Order is in the CHAT states: - If user is buyer: mark FIAT SENT and settle escrow! - If User is the seller and FIAT is SENT: Pay buyer invoice!''' + If user is buyer: mark FIAT SENT! + If User is the seller and FIAT is SENT: Settle escrow and pay buyer invoice!''' if order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Alternatively, if all collateral is locked? test out @@ -692,7 +686,7 @@ class Logics(): order.status = Order.Status.FSE order.is_fiat_sent = True - # If seller and fiat was sent, SETTLE ESCRO AND PAY BUYER INVOICE + # If seller and fiat was sent, SETTLE ESCROw AND PAY BUYER INVOICE elif cls.is_seller(order, user): if not order.is_fiat_sent: return False, {'bad_request':'You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer.'} @@ -706,7 +700,7 @@ class Logics(): # Double check the escrow is settled. if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash): - is_payed, context = cls.pay_buyer_invoice(order) ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!! + is_payed, context = follow_send_payment(order.buyer_invoice) ##### !!! KEY LINE - PAYS THE BUYER INVOICE !!! if is_payed: order.status = Order.Status.SUC order.buyer_invoice.status = LNPayment.Status.SUCCED @@ -716,9 +710,10 @@ class Logics(): # RETURN THE BONDS cls.return_bond(order.taker_bond) cls.return_bond(order.maker_bond) + return True, context else: # error handling here - pass + return False, context else: return False, {'bad_request':'You cannot confirm the fiat payment at this stage'} diff --git a/api/utils.py b/api/utils.py index e138d322..ec19d873 100644 --- a/api/utils.py +++ b/api/utils.py @@ -72,7 +72,7 @@ premium_percentile = {} @ring.dict(premium_percentile, expire=300) def compute_premium_percentile(order): - queryset = Order.objects.filter(currency=order.currency, status=Order.Status.PUB) + queryset = Order.objects.filter(currency=order.currency, status=Order.Status.PUB).exclude(id=order.id) print(len(queryset)) if len(queryset) <= 1: @@ -84,5 +84,8 @@ def compute_premium_percentile(order): rates.append(float(similar_order.last_satoshis) / float(similar_order.amount)) rates = np.array(rates) + print(rates) + print(order_rate) + print(np.sum(rates < order_rate)) return round(np.sum(rates < order_rate) / len(rates),2) diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index 138e5eb7..cebc14cb 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -10,7 +10,7 @@ export default class BookPage extends Component { this.state = { orders: new Array({id:0,}), currency: 0, - type: 1, + type: 2, currencies_dict: {"0":"ANY"}, loading: true, }; From 300a3855897e9c80056a4449aac98c7bffa00e82 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Wed, 19 Jan 2022 14:44:31 -0800 Subject: [PATCH 04/42] Fix chat reloading after confirming fiat sent --- frontend/src/components/OrderPage.js | 91 ++++++++++++++-------------- frontend/src/components/TradeBox.js | 44 ++++++++++---- 2 files changed, 77 insertions(+), 58 deletions(-) diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index b415a07b..6e5ebd0c 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -73,52 +73,55 @@ export default class OrderPage extends Component { } } + // Unneeded for the most part. Let's keep variable names as they come from the API + // Will need some renaming everywhere, but will decrease the mess. + setStateCool=(data)=>{ + this.setState({ + loading: false, + delay: this.setDelay(data.status), + id: data.id, + statusCode: data.status, + statusText: data.status_message, + type: data.type, + currency: data.currency, + currencyCode: this.getCurrencyCode(data.currency), + amount: data.amount, + paymentMethod: data.payment_method, + isExplicit: data.is_explicit, + premium: data.premium, + satoshis: data.satoshis, + makerId: data.maker, + isParticipant: data.is_participant, + urNick: data.ur_nick, + makerNick: data.maker_nick, + takerId: data.taker, + takerNick: data.taker_nick, + isMaker: data.is_maker, + isTaker: data.is_taker, + isBuyer: data.is_buyer, + isSeller: data.is_seller, + penalty: data.penalty, + expiresAt: data.expires_at, + badRequest: data.bad_request, + bondInvoice: data.bond_invoice, + bondSatoshis: data.bond_satoshis, + escrowInvoice: data.escrow_invoice, + escrowSatoshis: data.escrow_satoshis, + invoiceAmount: data.invoice_amount, + total_secs_expiry: data.total_secs_exp, + numSimilarOrders: data.num_similar_orders, + priceNow: data.price_now, + premiumNow: data.premium_now, + robotsInBook: data.robots_in_book, + premiumPercentile: data.premium_percentile, + numSimilarOrders: data.num_similar_orders + }) + } getOrderDetails() { this.setState(null) fetch('/api/order' + '?order_id=' + this.orderId) .then((response) => response.json()) - .then((data) => {console.log(data) & - this.setState({ - loading: false, - delay: this.setDelay(data.status), - id: data.id, - statusCode: data.status, - statusText: data.status_message, - type: data.type, - currency: data.currency, - currencyCode: this.getCurrencyCode(data.currency), - amount: data.amount, - paymentMethod: data.payment_method, - isExplicit: data.is_explicit, - premium: data.premium, - satoshis: data.satoshis, - makerId: data.maker, - isParticipant: data.is_participant, - urNick: data.ur_nick, - makerNick: data.maker_nick, - takerId: data.taker, - takerNick: data.taker_nick, - isMaker: data.is_maker, - isTaker: data.is_taker, - isBuyer: data.is_buyer, - isSeller: data.is_seller, - penalty: data.penalty, - expiresAt: data.expires_at, - badRequest: data.bad_request, - bondInvoice: data.bond_invoice, - bondSatoshis: data.bond_satoshis, - escrowInvoice: data.escrow_invoice, - escrowSatoshis: data.escrow_satoshis, - invoiceAmount: data.invoice_amount, - total_secs_expiry: data.total_secs_exp, - numSimilarOrders: data.num_similar_orders, - priceNow: data.price_now, - premiumNow: data.premium_now, - robotsInBook: data.robots_in_book, - premiumPercentile: data.premium_percentile, - numSimilarOrders: data.num_similar_orders - }) - }); + .then((data) => this.setStateCool(data)); } // These are used to refresh the data @@ -197,9 +200,7 @@ export default class OrderPage extends Component { }; fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions) .then((response) => response.json()) - .then((data) => (this.setState({badRequest:data.bad_request}) - & console.log(data) - & this.getOrderDetails(data.id))); + .then((data) => this.setStateCool(data)); } getCurrencyDict() { fetch('/static/assets/currencies.json') diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index b4c865dd..7b91e9cc 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -256,7 +256,7 @@ export default class TradeBox extends Component { - + @@ -526,7 +526,31 @@ handleRatingChange=(e)=>{ ) } - showChat(sendFiatButton, receivedFiatButton, openDisputeButton){ + showChat=()=>{ + //In Chatroom - No fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) + if(this.props.data.isBuyer & this.props.data.statusCode == 9){ + var showSendButton=true; + var showReveiceButton=false; + var showDisputeButton=true; + } + if(this.props.data.isSeller & this.props.data.statusCode == 9){ + var showSendButton=false; + var showReveiceButton=false; + var showDisputeButton=true; + } + + //In Chatroom - Fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) + if(this.props.data.isBuyer & this.props.data.statusCode == 10){ + var showSendButton=false; + var showReveiceButton=false; + var showDisputeButton=true; + } + if(this.props.data.isSeller & this.props.data.statusCode == 10){ + var showSendButton=false; + var showReveiceButton=true; + var showDisputeButton=true; + } + return( @@ -548,11 +572,10 @@ handleRatingChange=(e)=>{ - - {openDisputeButton ? this.showOpenDisputeButton() : ""} - {sendFiatButton ? this.showFiatSentButton() : ""} - {receivedFiatButton ? this.showFiatReceivedButton() : ""} + {showDisputeButton ? this.showOpenDisputeButton() : ""} + {showSendButton ? this.showFiatSentButton() : ""} + {showReveiceButton ? this.showFiatReceivedButton() : ""} {this.showBondIsLocked()} @@ -605,13 +628,8 @@ handleRatingChange=(e)=>{ {this.props.data.isBuyer & this.props.data.statusCode == 7 ? this.showWaitingForEscrow() : ""} {this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""} - {/* In Chatroom - No fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) */} - {this.props.data.isBuyer & this.props.data.statusCode == 9 ? this.showChat(true,false,true) : ""} - {this.props.data.isSeller & this.props.data.statusCode == 9 ? this.showChat(false,false,true) : ""} - - {/* In Chatroom - Fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) */} - {this.props.data.isBuyer & this.props.data.statusCode == 10 ? this.showChat(false,false,true) : ""} - {this.props.data.isSeller & this.props.data.statusCode == 10 ? this.showChat(false,true,true) : ""} + {/* In Chatroom */} + {this.props.data.statusCode == 9 || this.props.data.statusCode == 10 ? this.showChat(): ""} {/* Trade Finished */} {(this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15) ? this.showRateSelect() : ""} From 4eaad57475eac8252eb3afc8d6062402c0afe0c2 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Thu, 20 Jan 2022 09:30:29 -0800 Subject: [PATCH 05/42] background process debugging --- api/admin.py | 2 +- api/logics.py | 19 ++++++++++++++----- api/management/commands/clean_orders.py | 4 +++- api/models.py | 3 +-- clean_orders | 2 ++ 5 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 clean_orders diff --git a/api/admin.py b/api/admin.py index ff7f2c68..1cac1499 100644 --- a/api/admin.py +++ b/api/admin.py @@ -33,7 +33,7 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin): @admin.register(LNPayment) class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin): list_display = ('hash','concept','status','num_satoshis','type','expires_at','sender_link','receiver_link','order_made','order_taken','order_escrow','order_paid') - list_display_links = ('hash','concept','order_made','order_taken','order_escrow','order_paid') + list_display_links = ('hash','concept') change_links = ('sender','receiver') list_filter = ('type','concept','status') diff --git a/api/logics.py b/api/logics.py index ff6b3e89..43c798df 100644 --- a/api/logics.py +++ b/api/logics.py @@ -220,7 +220,7 @@ class Logics(): @classmethod def open_dispute(cls, order, user=None): - # Always settle the escrow during a dispute (same as with 'Fiat Sent') + # Always settle the escrow during a dispute # Dispute winner will have to submit a new invoice. if not order.trade_escrow.status == LNPayment.Status.SETLED: @@ -235,7 +235,10 @@ class Logics(): if not user == None: profile = user.profile profile.num_disputes = profile.num_disputes + 1 - profile.orders_disputes_started = list(profile.orders_disputes_started).append(str(order.id)) + if profile.orders_disputes_started == None: + profile.orders_disputes_started = [str(order.id)] + else: + profile.orders_disputes_started = list(profile.orders_disputes_started).append(str(order.id)) profile.save() return True, None @@ -325,7 +328,7 @@ class Logics(): ''' adds a new rating to a user profile''' # TODO Unsafe, does not update ratings, it adds more ratings everytime a new rating is clicked. - profile.total_ratings = profile.total_ratings + 1 + profile.total_ratings += 1 latest_ratings = profile.latest_ratings if latest_ratings == None: profile.latest_ratings = [rating] @@ -633,6 +636,7 @@ class Logics(): '''returns the trade escrow''' if LNNode.cancel_return_hold_invoice(order.trade_escrow.payment_hash): order.trade_escrow.status = LNPayment.Status.RETNED + order.trade_escrow.save() return True def cancel_escrow(order): @@ -640,6 +644,7 @@ class Logics(): # Same as return escrow, but used when the invoice was never LOCKED if LNNode.cancel_return_hold_invoice(order.trade_escrow.payment_hash): order.trade_escrow.status = LNPayment.Status.CANCEL + order.trade_escrow.save() return True def return_bond(bond): @@ -649,10 +654,12 @@ class Logics(): try: LNNode.cancel_return_hold_invoice(bond.payment_hash) bond.status = LNPayment.Status.RETNED + bond.save() return True except Exception as e: if 'invoice already settled' in str(e): bond.status = LNPayment.Status.SETLED + bond.save() return True else: raise e @@ -665,10 +672,12 @@ class Logics(): try: LNNode.cancel_return_hold_invoice(bond.payment_hash) bond.status = LNPayment.Status.CANCEL + bond.save() return True except Exception as e: if 'invoice already settled' in str(e): bond.status = LNPayment.Status.SETLED + bond.save() return True else: raise e @@ -705,11 +714,11 @@ class Logics(): order.status = Order.Status.SUC order.buyer_invoice.status = LNPayment.Status.SUCCED order.expires_at = timezone.now() + timedelta(seconds=Order.t_to_expire[Order.Status.SUC]) - order.save() - # RETURN THE BONDS cls.return_bond(order.taker_bond) cls.return_bond(order.maker_bond) + order.save() + return True, context else: # error handling here diff --git a/api/management/commands/clean_orders.py b/api/management/commands/clean_orders.py index 6e5faf81..9d4cfc67 100644 --- a/api/management/commands/clean_orders.py +++ b/api/management/commands/clean_orders.py @@ -40,11 +40,13 @@ class Command(BaseCommand): if Logics.order_expires(order): # Order send to expire here debug['expired_orders'].append({idx:context}) - # If it cannot locate the hold invoice, make it expire anywway + # It should not happen, but if it cannot locate the hold invoice + # it probably was cancelled by another thread, make it expire anyway. except Exception as e: if 'unable to locate invoice' in str(e): self.stdout.write(str(e)) order.status = Order.Status.EXP + order.save() debug['expired_orders'].append({idx:context}) diff --git a/api/models.py b/api/models.py index 673e25d8..52997568 100644 --- a/api/models.py +++ b/api/models.py @@ -57,7 +57,6 @@ class LNPayment(models.Model): FLIGHT = 7, 'In flight' SUCCED = 8, 'Succeeded' FAILRO = 9, 'Routing failed' - # payment use details type = models.PositiveSmallIntegerField(choices=Types.choices, null=False, default=Types.HOLD) @@ -141,7 +140,7 @@ class Order(models.Model): last_satoshis = models.PositiveBigIntegerField(null=True, validators=[MinValueValidator(0), MaxValueValidator(MAX_TRADE*2)], blank=True) # sats last time checked. Weird if 2* trade max... # order participants - maker = models.ForeignKey(User, related_name='maker', on_delete=models.CASCADE, null=True, default=None) # unique = True, a maker can only make one order + maker = models.ForeignKey(User, related_name='maker', on_delete=models.SET_NULL, null=True, default=None) # unique = True, a maker can only make one order taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None, blank=True) # unique = True, a taker can only take one order is_pending_cancel = models.BooleanField(default=False, null=False) # When collaborative cancel is needed and one partner has cancelled. is_fiat_sent = models.BooleanField(default=False, null=False) diff --git a/clean_orders b/clean_orders new file mode 100644 index 00000000..dbc4464b --- /dev/null +++ b/clean_orders @@ -0,0 +1,2 @@ +2022-01-20 12:59:13.516882+00:00 +{'num_expired_orders': 1, 'expired_orders': [{0: 'Order 56: SELL BTC for 40.0 USD was Public'}]} From fb5cb8fb4aa33a8282e0371d64303f632e58f33d Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Thu, 20 Jan 2022 12:50:25 -0800 Subject: [PATCH 06/42] Cancel frontend clean up. Try and go for management commands --- api/admin.py | 4 ++-- api/logics.py | 23 ++++++++++++++++------ api/management/commands/clean_orders.py | 12 ++++++++++- api/management/commands/follow_invoices.py | 17 +++++++++++++--- clean_orders | 2 -- frontend/src/components/Chat.js | 5 ++--- frontend/src/components/InfoDialog.js | 12 +++++------ frontend/src/components/OrderPage.js | 2 +- 8 files changed, 53 insertions(+), 24 deletions(-) delete mode 100644 clean_orders diff --git a/api/admin.py b/api/admin.py index 1cac1499..19875946 100644 --- a/api/admin.py +++ b/api/admin.py @@ -32,9 +32,9 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin): @admin.register(LNPayment) class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin): - list_display = ('hash','concept','status','num_satoshis','type','expires_at','sender_link','receiver_link','order_made','order_taken','order_escrow','order_paid') + list_display = ('hash','concept','status','num_satoshis','type','expires_at','sender_link','receiver_link','order_made_link','order_taken_link','order_escrow_link','order_paid_link') list_display_links = ('hash','concept') - change_links = ('sender','receiver') + change_links = ('sender','receiver','order_made','order_taken','order_escrow','order_paid') list_filter = ('type','concept','status') @admin.register(Profile) diff --git a/api/logics.py b/api/logics.py index 43c798df..2cb803e0 100644 --- a/api/logics.py +++ b/api/logics.py @@ -111,13 +111,13 @@ class Logics(): # Do not change order status if an order in any with # any of these status is sent to expire here - do_nothing = [Order.Status.DEL, Order.Status.UCA, + does_not_expire = [Order.Status.DEL, Order.Status.UCA, Order.Status.EXP, Order.Status.TLD, Order.Status.DIS, Order.Status.CCA, Order.Status.PAY, Order.Status.SUC, Order.Status.FAI, Order.Status.MLD] - if order.status in do_nothing: + if order.status in does_not_expire: return False elif order.status == Order.Status.WFB: @@ -283,7 +283,7 @@ class Logics(): if not order.taker_bond: return False, {'bad_request':'Wait for your order to be taken.'} if not (order.taker_bond.status == order.maker_bond.status == LNPayment.Status.LOCKED): - return False, {'bad_request':'You cannot a invoice while bonds are not posted.'} + return False, {'bad_request':'You cannot submit a invoice while bonds are not locked.'} num_satoshis = cls.buyer_invoice_amount(order, user)[1]['invoice_amount'] buyer_invoice = LNNode.validate_ln_invoice(invoice, num_satoshis) @@ -357,6 +357,17 @@ class Logics(): @classmethod def cancel_order(cls, order, user, state=None): + # Do not change order status if an order in any with + # any of these status is sent to expire here + do_not_cancel = [Order.Status.DEL, Order.Status.UCA, + Order.Status.EXP, Order.Status.TLD, + Order.Status.DIS, Order.Status.CCA, + Order.Status.PAY, Order.Status.SUC, + Order.Status.FAI, Order.Status.MLD] + + if order.status in do_not_cancel: + return False, {'bad_request':'You cannot cancel this order'} + # 1) When maker cancels before bond '''The order never shows up on the book and order status becomes "cancelled". That's it.''' @@ -685,8 +696,8 @@ class Logics(): @classmethod def confirm_fiat(cls, order, user): ''' If Order is in the CHAT states: - If user is buyer: mark FIAT SENT! - If User is the seller and FIAT is SENT: Settle escrow and pay buyer invoice!''' + If user is buyer: fiat_sent goes to true. + If User is tseller and fiat_sent is true: settle the escrow and pay buyer invoice!''' if order.status == Order.Status.CHA or order.status == Order.Status.FSE: # TODO Alternatively, if all collateral is locked? test out @@ -695,7 +706,7 @@ class Logics(): order.status = Order.Status.FSE order.is_fiat_sent = True - # If seller and fiat was sent, SETTLE ESCROw AND PAY BUYER INVOICE + # If seller and fiat was sent, SETTLE ESCROW AND PAY BUYER INVOICE elif cls.is_seller(order, user): if not order.is_fiat_sent: return False, {'bad_request':'You cannot confirm to have received the fiat before it is confirmed to be sent by the buyer.'} diff --git a/api/management/commands/clean_orders.py b/api/management/commands/clean_orders.py index 9d4cfc67..923ba3e7 100644 --- a/api/management/commands/clean_orders.py +++ b/api/management/commands/clean_orders.py @@ -11,7 +11,7 @@ class Command(BaseCommand): # def add_arguments(self, parser): # parser.add_argument('debug', nargs='+', type=boolean) - def handle(self, *args, **options): + def clean_orders(self, *args, **options): ''' Continuously checks order expiration times for 1 hour. If order has expires, it calls the logics module for expiration handling.''' @@ -53,3 +53,13 @@ class Command(BaseCommand): if debug['num_expired_orders'] > 0: self.stdout.write(str(timezone.now())) self.stdout.write(str(debug)) + + def handle(self, *args, **options): + ''' Never mind database locked error, keep going, print them out''' + try: + self.clean_orders() + except Exception as e: + if 'database is locked' in str(e): + self.stdout.write('database is locked') + + self.stdout.write(e) diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py index 5a9840bd..c88ee749 100644 --- a/api/management/commands/follow_invoices.py +++ b/api/management/commands/follow_invoices.py @@ -27,7 +27,7 @@ class Command(BaseCommand): # def add_arguments(self, parser): # parser.add_argument('debug', nargs='+', type=boolean) - def handle(self, *args, **options): + def follow_invoices(self, *args, **options): ''' Follows and updates LNpayment objects until settled or canceled''' @@ -66,7 +66,7 @@ class Command(BaseCommand): # If it fails at finding the invoice it has been canceled. # On RoboSats DB we make a distinction between cancelled and returned (LND does not) if 'unable to locate invoice' in str(e): - self.stdout.write('unable to locate invoice') + self.stdout.write(str(e)) hold_lnpayment.status = LNPayment.Status.CANCEL # LND restarted. if 'wallet locked, unlock it' in str(e): @@ -130,4 +130,15 @@ class Command(BaseCommand): # TODO If a lnpayment goes from LOCKED to INVGED. Totally weird # halt the order if lnpayment.status == LNPayment.Status.LOCKED: - pass \ No newline at end of file + pass + + def handle(self, *args, **options): + ''' Never mind database locked error, keep going, print them out''' + + try: + self.follow_invoices() + except Exception as e: + if 'database is locked' in str(e): + self.stdout.write('database is locked') + + self.stdout.write(e) \ No newline at end of file diff --git a/clean_orders b/clean_orders deleted file mode 100644 index dbc4464b..00000000 --- a/clean_orders +++ /dev/null @@ -1,2 +0,0 @@ -2022-01-20 12:59:13.516882+00:00 -{'num_expired_orders': 1, 'expired_orders': [{0: 'Order 56: SELL BTC for 40.0 USD was Public'}]} diff --git a/frontend/src/components/Chat.js b/frontend/src/components/Chat.js index e1b3dc22..d38441fd 100644 --- a/frontend/src/components/Chat.js +++ b/frontend/src/components/Chat.js @@ -1,8 +1,6 @@ import React, { Component } from 'react'; import { w3cwebsocket as W3CWebSocket } from "websocket"; -import {Button, TextField, Link, Grid, Typography, Container, Card, CardHeader, Paper, Avatar} from "@mui/material"; -import { withStyles } from "@mui/material"; - +import {Button, TextField, Grid, Container, Card, CardHeader, Paper, Avatar, FormHelperText} from "@mui/material"; export default class Chat extends Component { @@ -113,6 +111,7 @@ export default class Chat extends Component { + This chat has no memory. If you reload the page messages are lost. ) } diff --git a/frontend/src/components/InfoDialog.js b/frontend/src/components/InfoDialog.js index f48ac387..d06baf6b 100644 --- a/frontend/src/components/InfoDialog.js +++ b/frontend/src/components/InfoDialog.js @@ -10,7 +10,7 @@ export default class InfoDialog extends Component { What is RoboSats?

It is a BTC/FIAT peer-to-peer exchange over lightning. It simplifies - matchmaking and minimizes the trust needed to trade with a peer.

+ matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.

RoboSats is an open source project (GitHub). @@ -19,18 +19,18 @@ export default class InfoDialog extends Component { How does it work? -

Anonymous AdequateAlice01 wants to sell bitcoin. She posts a sell order. +

AnonymousAlice01 wants to sell bitcoin. She posts a sell order. BafflingBob02 wants to buy bitcoin and he takes Alice's order. Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning - hold invoice. RoboSats locks the invoice until Bob confirms he sent - the fiat to Alice. Once Alice confirms she received the fiat, she - tells RoboSats to release the satoshis to Bob. Enjoy your satoshis, + hold invoice. RoboSats locks the invoice until Alice confirms she + received the fiat. Then the satoshis to Bob. Enjoy your satoshis, Bob!

At no point, AdequateAlice01 and BafflingBob02 have to trust the bitcoin to each other. In case they have a conflict, RoboSats staff - will help resolving the dispute.

+ will help resolving the dispute. You can find an step-by-step + description of the trade pipeline on our GitHub Readme

What payment methods are accepted? diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index 6e5ebd0c..1b27044d 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -279,7 +279,7 @@ export default class OrderPage extends Component { )} // If the order does not yet have an escrow deposited. Show dialog // to confirm forfeiting the bond - if (this.state.statusCode < 8){ + if (this.state.statusCode in [0,1,3,6,7]){ return( From 28abd0380f51ea1cdecbb4366c4c581cc3d2e515 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Sat, 22 Jan 2022 09:01:57 -0800 Subject: [PATCH 07/42] FIx collection of typos --- README.md | 2 +- api/management/commands/clean_orders.py | 2 +- api/models.py | 4 ++-- frontend/src/components/InfoDialog.js | 14 +++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 71503250..224c0bf5 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Alice wants to buy satoshis privately: See [CONTRIBUTING.md](CONTRIBUTING.md) ## Original idea -The concept of a simple custody-minimized lightning exchange using hold invoices is heavily inspired by [P2PLNBOT](https://github.com/grunch/p2plnbot) by @grunch +The concept of a simple custody-minimized lightning exchange using hold invoices is heavily inspired in [P2PLNBOT](https://github.com/grunch/p2plnbot) by @grunch ## License diff --git a/api/management/commands/clean_orders.py b/api/management/commands/clean_orders.py index 923ba3e7..845a2c8b 100644 --- a/api/management/commands/clean_orders.py +++ b/api/management/commands/clean_orders.py @@ -22,7 +22,7 @@ class Command(BaseCommand): Order.Status.DIS, Order.Status.CCA, Order.Status.PAY, Order.Status.SUC, Order.Status.FAI, Order.Status.MLD, - Order.Status.TLD] + Order.Status.TLD, Order.Status.WFR] while True: time.sleep(5) diff --git a/api/models.py b/api/models.py index 52997568..01ef8117 100644 --- a/api/models.py +++ b/api/models.py @@ -175,12 +175,12 @@ class Order(models.Model): 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 : 10*24*60*60, # 'In dispute' + 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 : 24*60*60, # 'Wait for dispute resolution' + 16 : 10*24*60*60, # 'Wait for dispute resolution' 17 : 24*60*60, # 'Maker lost dispute' 18 : 24*60*60, # 'Taker lost dispute' } diff --git a/frontend/src/components/InfoDialog.js b/frontend/src/components/InfoDialog.js index d06baf6b..a49f06e4 100644 --- a/frontend/src/components/InfoDialog.js +++ b/frontend/src/components/InfoDialog.js @@ -24,13 +24,13 @@ export default class InfoDialog extends Component { Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning hold invoice. RoboSats locks the invoice until Alice confirms she - received the fiat. Then the satoshis to Bob. Enjoy your satoshis, + received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, Bob!

At no point, AdequateAlice01 and BafflingBob02 have to trust the bitcoin to each other. In case they have a conflict, RoboSats staff - will help resolving the dispute. You can find an step-by-step - description of the trade pipeline on our GitHub Readme

+ will help resolving the dispute. You can find a step-by-step + description of the trade pipeline in 'How it works'

What payment methods are accepted? @@ -79,10 +79,10 @@ export default class InfoDialog extends Component { In addition, disputes are solved by the RoboSats staff.

-

Trust requirements are minimized, however there is still one way - RoboSats could run away with your satoshis, by not releasing - the satoshis to the buyer. It could be argued that such move is not on - RoboSats interest as it would damage thereputation for a small payout. +

Trust requirements are minimal, however there is still one way RoboSats + could run away with your satoshis: by not releasing + the satoshis to the buyer. It could be argued that such move is not on RoboSats + interest as it would damage the reputation for a small payout. However, you should hesitate and only trade small quantities at a time. For large amounts use an onchain escrow service such as Bisq

From 8a43d3359d5d669a4567db2fbaef322eb1117366 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Sat, 22 Jan 2022 15:05:03 -0800 Subject: [PATCH 08/42] Simplify orderpage vars, improve tradebox response times --- api/views.py | 8 ++ frontend/src/components/BookPage.js | 3 + frontend/src/components/Chat.js | 4 +- frontend/src/components/OrderPage.js | 142 +++++++++++---------- frontend/src/components/TradeBox.js | 179 +++++++++++++++------------ 5 files changed, 188 insertions(+), 148 deletions(-) diff --git a/api/views.py b/api/views.py index 6e529a40..6141c20d 100644 --- a/api/views.py +++ b/api/views.py @@ -217,6 +217,14 @@ class OrderView(viewsets.ViewSet): # add whether a collaborative cancel is pending data['pending_cancel'] = order.is_pending_cancel + # 9) If status is 'DIS' and all HTLCS are in LOCKED + elif order.status == Order.Status.DIS:# TODO Add the other status + + # add whether the dispute statement has been received + if data['is_maker']: + data['statement_submitted'] = (order.maker_statement != None and order.maker_statement != "") + elif data['is_taker']: + data['statement_submitted'] = (order.taker_statement != None and order.maker_statement != "") return Response(data, status.HTTP_200_OK) diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index cebc14cb..241c5273 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -194,11 +194,14 @@ export default class BookPage extends Component { No orders found to {this.state.type == 0 ? ' sell ' :' buy ' } BTC for {this.state.currencyCode}
+
Be the first one to create an order +
+
) : diff --git a/frontend/src/components/Chat.js b/frontend/src/components/Chat.js index d38441fd..e9445554 100644 --- a/frontend/src/components/Chat.js +++ b/frontend/src/components/Chat.js @@ -51,7 +51,7 @@ export default class Chat extends Component { this.client.send(JSON.stringify({ type: "message", message: this.state.value, - nick: this.props.urNick, + nick: this.props.ur_nick, })); this.state.value = '' e.preventDefault(); @@ -64,7 +64,7 @@ export default class Chat extends Component { {this.state.messages.map(message => <> {/* If message sender is not our nick, gray color, if it is our nick, green color */} - {message.userNick == this.props.urNick ? + {message.userNick == this.props.ur_nick ? { - this.setState({ + completeSetState=(newStateVars)=>{ + console.log(newStateVars) + var otherStateVars = { loading: false, - delay: this.setDelay(data.status), - id: data.id, - statusCode: data.status, - statusText: data.status_message, - type: data.type, - currency: data.currency, - currencyCode: this.getCurrencyCode(data.currency), - amount: data.amount, - paymentMethod: data.payment_method, - isExplicit: data.is_explicit, - premium: data.premium, - satoshis: data.satoshis, - makerId: data.maker, - isParticipant: data.is_participant, - urNick: data.ur_nick, - makerNick: data.maker_nick, - takerId: data.taker, - takerNick: data.taker_nick, - isMaker: data.is_maker, - isTaker: data.is_taker, - isBuyer: data.is_buyer, - isSeller: data.is_seller, - penalty: data.penalty, - expiresAt: data.expires_at, - badRequest: data.bad_request, - bondInvoice: data.bond_invoice, - bondSatoshis: data.bond_satoshis, - escrowInvoice: data.escrow_invoice, - escrowSatoshis: data.escrow_satoshis, - invoiceAmount: data.invoice_amount, - total_secs_expiry: data.total_secs_exp, - numSimilarOrders: data.num_similar_orders, - priceNow: data.price_now, - premiumNow: data.premium_now, - robotsInBook: data.robots_in_book, - premiumPercentile: data.premium_percentile, - numSimilarOrders: data.num_similar_orders - }) + delay: this.setDelay(newStateVars.status), + currencyCode: this.getCurrencyCode(newStateVars.currency), + }; + console.log(otherStateVars) + var completeStateVars = Object.assign({}, newStateVars, otherStateVars); + console.log(completeStateVars) + this.setState(completeStateVars); + // { + // loading: false, + // delay: this.setDelay(data.status), + // id: data.id, + // status: data.status, + // status_message: data.status_message, + // type: data.type, + // currency: data.currency, + // currencyCode: this.getCurrencyCode(data.currency), + // amount: data.amount, + // payment_method: data.payment_method, + // isExplicit: data.is_explicit, + // premium: data.premium, + // satoshis: data.satoshis, + // makerId: data.maker, + // is_participant: data.is_participant, + // urNick: data.ur_nick, + // maker_nick: data.maker_nick, + // takerId: data.taker, + // taker_nick: data.taker_nick, + // is_maker: data.is_maker, + // is_taker: data.is_taker, + // is_buyer: data.is_buyer, + // is_seller: data.is_seller, + // penalty: data.penalty, + // expires_at: data.expires_at, + // bad_request: data.bad_request, + // bond_invoice: data.bond_invoice, + // bondSatoshis: data.bond_satoshis, + // escrow_invoice: data.escrow_invoice, + // escrowSatoshis: data.escrow_satoshis, + // invoice_amount: data.invoice_amount, + // total_secs_exp: data.total_secs_exp, + // num_similar_orders: data.num_similar_orders, + // price_now: data.price_now, + // premium_now: data.premium_now, + // probots_in_book: data.robots_in_book, + // premium_percentile: data.premium_percentile, + // num_similar_orders: data.num_similar_orders + // }) } getOrderDetails() { this.setState(null) fetch('/api/order' + '?order_id=' + this.orderId) .then((response) => response.json()) - .then((data) => this.setStateCool(data)); + .then((data) => this.completeSetState(data)); } // These are used to refresh the data @@ -153,7 +163,7 @@ export default class OrderPage extends Component { } else { var col = 'black' - var fraction_left = (total/1000) / this.state.total_secs_expiry + var fraction_left = (total/1000) / this.state.total_secs_exp // Make orange at 25% of time left if (fraction_left < 0.25){col = 'orange'} // Make red at 10% of time left @@ -172,8 +182,8 @@ export default class OrderPage extends Component { React.useEffect(() => { const timer = setInterval(() => { setProgress((oldProgress) => { - var left = calcTimeDelta( new Date(this.state.expiresAt)).total /1000; - return (left / this.state.total_secs_expiry) * 100; + var left = calcTimeDelta( new Date(this.state.expires_at)).total /1000; + return (left / this.state.total_secs_exp) * 100; }); }, 1000); @@ -200,7 +210,7 @@ export default class OrderPage extends Component { }; fetch('/api/order/' + '?order_id=' + this.orderId, requestOptions) .then((response) => response.json()) - .then((data) => this.setStateCool(data)); + .then((data) => this.completeSetState(data)); } getCurrencyDict() { fetch('/static/assets/currencies.json') @@ -271,7 +281,7 @@ export default class OrderPage extends Component { // If maker and Waiting for Bond. Or if taker and Waiting for bond. // Simply allow to cancel without showing the cancel dialog. - if ((this.state.isMaker & this.state.statusCode == 0) || this.state.isTaker & this.state.statusCode == 3){ + if ((this.state.is_maker & this.state.status == 0) || this.state.is_taker & this.state.status == 3){ return( @@ -279,7 +289,7 @@ export default class OrderPage extends Component { )} // If the order does not yet have an escrow deposited. Show dialog // to confirm forfeiting the bond - if (this.state.statusCode in [0,1,3,6,7]){ + if (this.state.status in [0,1,3,6,7]){ return( @@ -305,24 +315,24 @@ export default class OrderPage extends Component { - + - {this.state.isParticipant ? + {this.state.is_participant ? <> - {this.state.takerNick!='None' ? + {this.state.taker_nick!='None' ? <> - + @@ -334,7 +344,7 @@ export default class OrderPage extends Component { - + @@ -353,7 +363,7 @@ export default class OrderPage extends Component { - + @@ -362,8 +372,8 @@ export default class OrderPage extends Component { - {this.state.priceNow? - + {this.state.price_now? + : (this.state.isExplicit ? @@ -386,7 +396,7 @@ export default class OrderPage extends Component { - + @@ -408,7 +418,7 @@ export default class OrderPage extends Component { {/* Participants can see the "Cancel" Button, but cannot see the "Back" or "Take Order" buttons */} - {this.state.isParticipant ? + {this.state.is_participant ? : <> @@ -427,21 +437,21 @@ export default class OrderPage extends Component { orderDetailsPage (){ return( - this.state.badRequest ? + this.state.bad_request ?
- {this.state.badRequest}
+ {this.state.bad_request}
: - (this.state.isParticipant ? + (this.state.is_participant ? {this.orderBox()} - + : diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index 7b91e9cc..b132ebf4 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -61,7 +61,7 @@ export default class TradeBox extends Component { }; fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) .then((response) => response.json()) - .then((data) => (this.props.data = data)); + .then((data) => this.props.completeSetState(data)); this.handleClickCloseConfirmDispute(); } @@ -140,26 +140,26 @@ export default class TradeBox extends Component {
- {this.props.data.isMaker ? + {this.props.data.is_maker ? - Lock {pn(this.props.data.bondSatoshis)} Sats to PUBLISH order + Lock {pn(this.props.data.bond_satoshis)} Sats to PUBLISH order : - Lock {pn(this.props.data.bondSatoshis)} Sats to TAKE the order + Lock {pn(this.props.data.bond_satoshis)} Sats to TAKE the order } - - + + - 🔒 Your {this.props.data.isMaker ? 'maker' : 'taker'} bond is locked + 🔒 Your {this.props.data.is_maker ? 'maker' : 'taker'} bond is locked ); @@ -184,19 +184,19 @@ export default class TradeBox extends Component { - Deposit {pn(this.props.data.escrowSatoshis)} Sats as trade collateral + Deposit {pn(this.props.data.escrow_satoshis)} Sats as trade collateral - - + + - + @@ -272,7 +272,7 @@ export default class TradeBox extends Component { - @@ -305,7 +305,7 @@ export default class TradeBox extends Component { fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) .then((response) => response.json()) .then((data) => this.setState({badInvoice:data.bad_invoice}) - & console.log(data)); + & this.props.completeSetState(data)); } handleInputDisputeChanged=(e)=>{ @@ -329,7 +329,7 @@ export default class TradeBox extends Component { fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) .then((response) => response.json()) .then((data) => this.setState({badStatement:data.bad_statement}) - & console.log(data)); + & this.props.completeSetState(data)); } showInputInvoice(){ @@ -340,14 +340,14 @@ export default class TradeBox extends Component { - Submit a LN invoice for {pn(this.props.data.invoiceAmount)} Sats + Submit a LN invoice for {pn(this.props.data.invoice_amount)} Sats The taker is committed! Before letting you send {" "+ parseFloat(parseFloat(this.props.data.amount).toFixed(4))+ " "+ this.props.data.currencyCode}, we want to make sure you are able to receive the BTC. Please provide a - valid invoice for {pn(this.props.data.invoiceAmount)} Satoshis. + valid invoice for {pn(this.props.data.invoice_amount)} Satoshis. @@ -374,47 +374,65 @@ export default class TradeBox extends Component { } // Asks the user for a dispute statement. - showInDisputeStatement(){ - return ( - - // TODO Option to upload files - - - - - A dispute has been opened - + showInDisputeStatement=()=>{ + console.log(this.props.data.statement_submitted) + if(this.props.data.statement_submitted){ + return ( + + + + We have received your statement + + + + + We are waiting for your trade counterparty statement. + + + {this.showBondIsLocked()} - - - Please, submit your statement. Be clear and specific about what happened and provide the necessary - evidence. It is best to provide a burner email, XMPP or telegram username to follow up with the staff. - Disputes are solved at the discretion of real robots (aka humans), so be as helpful - as possible to ensure a fair outcome. Max 5000 chars. - + ) + }else{ + return ( + + // TODO Option to upload files + + + + + A dispute has been opened + + + + + Please, submit your statement. Be clear and specific about what happened and provide the necessary + evidence. It is best to provide a burner email, XMPP or telegram username to follow up with the staff. + Disputes are solved at the discretion of real robots (aka humans), so be as helpful + as possible to ensure a fair outcome. Max 5000 chars. + + + + + + + + + + + {this.showBondIsLocked()} - - - - - - - - - {this.showBondIsLocked()} - - ) + )} } showWaitingForEscrow(){ @@ -470,7 +488,7 @@ export default class TradeBox extends Component { }; fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) .then((response) => response.json()) - .then((data) => (this.props.data = data)); + .then((data) => this.props.completeSetState(data)); } handleRatingChange=(e)=>{ @@ -484,7 +502,7 @@ handleRatingChange=(e)=>{ }; fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions) .then((response) => response.json()) - .then((data) => (this.props.data = data)); + .then((data) => this.props.completeSetState(data)); } showFiatSentButton(){ @@ -528,24 +546,24 @@ handleRatingChange=(e)=>{ showChat=()=>{ //In Chatroom - No fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) - if(this.props.data.isBuyer & this.props.data.statusCode == 9){ + if(this.props.data.is_buyer & this.props.data.status == 9){ var showSendButton=true; var showReveiceButton=false; var showDisputeButton=true; } - if(this.props.data.isSeller & this.props.data.statusCode == 9){ + if(this.props.data.is_seller & this.props.data.status == 9){ var showSendButton=false; var showReveiceButton=false; var showDisputeButton=true; } //In Chatroom - Fiat sent - showChat(showSendButton, showReveiceButton, showDisputeButton) - if(this.props.data.isBuyer & this.props.data.statusCode == 10){ + if(this.props.data.is_buyer & this.props.data.status == 10){ var showSendButton=false; var showReveiceButton=false; var showDisputeButton=true; } - if(this.props.data.isSeller & this.props.data.statusCode == 10){ + if(this.props.data.is_seller & this.props.data.status == 10){ var showSendButton=false; var showReveiceButton=true; var showDisputeButton=true; @@ -555,11 +573,11 @@ handleRatingChange=(e)=>{ - Chatting with {this.props.data.isMaker ? this.props.data.takerNick : this.props.data.makerNick} + Chatting with {this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick} - {this.props.data.isSeller ? + {this.props.data.is_seller ? Say hi! Be helpful and concise. Let them know how to send you {this.props.data.currencyCode}. @@ -571,7 +589,7 @@ handleRatingChange=(e)=>{ - + {showDisputeButton ? this.showOpenDisputeButton() : ""} {showSendButton ? this.showFiatSentButton() : ""} @@ -592,7 +610,7 @@ handleRatingChange=(e)=>{ - What do you think of {this.props.data.isMaker ? this.props.data.takerNick : this.props.data.makerNick}? + What do you think of {this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}? @@ -616,33 +634,34 @@ handleRatingChange=(e)=>{ {/* Maker and taker Bond request */} - {this.props.data.bondInvoice ? this.showQRInvoice() : ""} + {this.props.data.is_maker & this.props.data.status == 0 ? this.showQRInvoice() : ""} + {this.props.data.is_taker & this.props.data.status == 3 ? this.showQRInvoice() : ""} {/* Waiting for taker and taker bond request */} - {this.props.data.isMaker & this.props.data.statusCode == 1 ? this.showMakerWait() : ""} - {this.props.data.isMaker & this.props.data.statusCode == 3 ? this.showTakerFound() : ""} + {this.props.data.is_maker & this.props.data.status == 1 ? this.showMakerWait() : ""} + {this.props.data.is_maker & this.props.data.status == 3 ? this.showTakerFound() : ""} {/* Send Invoice (buyer) and deposit collateral (seller) */} - {this.props.data.isSeller & this.props.data.escrowInvoice != null ? this.showEscrowQRInvoice() : ""} - {this.props.data.isBuyer & this.props.data.invoiceAmount != null ? this.showInputInvoice() : ""} - {this.props.data.isBuyer & this.props.data.statusCode == 7 ? this.showWaitingForEscrow() : ""} - {this.props.data.isSeller & this.props.data.statusCode == 8 ? this.showWaitingForBuyerInvoice() : ""} + {this.props.data.is_seller & (this.props.data.status == 6 || this.props.data.status == 7 ) ? this.showEscrowQRInvoice() : ""} + {this.props.data.is_buyer & (this.props.data.status == 6 || this.props.data.status == 8 )? this.showInputInvoice() : ""} + {this.props.data.is_buyer & this.props.data.status == 7 ? this.showWaitingForEscrow() : ""} + {this.props.data.is_seller & this.props.data.status == 8 ? this.showWaitingForBuyerInvoice() : ""} {/* In Chatroom */} - {this.props.data.statusCode == 9 || this.props.data.statusCode == 10 ? this.showChat(): ""} + {this.props.data.status == 9 || this.props.data.status == 10 ? this.showChat(): ""} {/* Trade Finished */} - {(this.props.data.isSeller & this.props.data.statusCode > 12 & this.props.data.statusCode < 15) ? this.showRateSelect() : ""} - {(this.props.data.isBuyer & this.props.data.statusCode == 14) ? this.showRateSelect() : ""} + {(this.props.data.is_seller & this.props.data.status > 12 & this.props.data.status < 15) ? this.showRateSelect() : ""} + {(this.props.data.is_buyer & this.props.data.status == 14) ? this.showRateSelect() : ""} {/* Trade Finished - Payment Routing Failed */} - {this.props.data.isBuyer & this.props.data.statusCode == 15 ? this.showUpdateInvoice() : ""} + {this.props.data.is_buyer & this.props.data.status == 15 ? this.showUpdateInvoice() : ""} {/* Trade Finished - TODO Needs more planning */} - {this.props.data.statusCode == 11 ? this.showInDisputeStatement() : ""} + {this.props.data.status == 11 ? this.showInDisputeStatement() : ""} {/* Order has expired */} - {this.props.data.statusCode == 5 ? this.showOrderExpired() : ""} + {this.props.data.status == 5 ? this.showOrderExpired() : ""} {/* TODO */} {/* */} {/* */} From 6377b052ce993b46673ebdb1a10dcf2bdaf1431b Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Sun, 23 Jan 2022 04:30:41 -0800 Subject: [PATCH 09/42] Add sounds to tradebox --- api/management/commands/follow_invoices.py | 1 - api/models.py | 4 +-- frontend/src/components/TradeBox.js | 28 ++++++++++++++++-- frontend/static/assets/sounds/chat-open.mp3 | Bin 0 -> 92368 bytes .../static/assets/sounds/locked-invoice.mp3 | Bin 0 -> 92368 bytes frontend/static/assets/sounds/sucessful.mp3 | Bin 0 -> 64364 bytes frontend/static/assets/sounds/taker-found.mp3 | Bin 0 -> 111176 bytes 7 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 frontend/static/assets/sounds/chat-open.mp3 create mode 100644 frontend/static/assets/sounds/locked-invoice.mp3 create mode 100644 frontend/static/assets/sounds/sucessful.mp3 create mode 100644 frontend/static/assets/sounds/taker-found.mp3 diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py index c88ee749..eaa36601 100644 --- a/api/management/commands/follow_invoices.py +++ b/api/management/commands/follow_invoices.py @@ -5,7 +5,6 @@ from api.models import LNPayment, Order from api.logics import Logics from django.utils import timezone -from datetime import timedelta from decouple import config from base64 import b64decode import time diff --git a/api/models.py b/api/models.py index 01ef8117..942d811a 100644 --- a/api/models.py +++ b/api/models.py @@ -19,7 +19,7 @@ BOND_SIZE = float(config('BOND_SIZE')) class Currency(models.Model): - currency_dict = json.load(open('./frontend/static/assets/currencies.json')) + currency_dict = json.load(open('frontend/static/assets/currencies.json')) currency_choices = [(int(val), label) for val, label in list(currency_dict.items())] currency = models.PositiveSmallIntegerField(choices=currency_choices, null=False, unique=True) @@ -175,7 +175,7 @@ class Order(models.Model): 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' + 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' diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index b132ebf4..1d7b90ac 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -44,6 +44,17 @@ export default class TradeBox extends Component { } } + Sound = ({soundFileName}) => ( + // Four filenames: "locked-invoice", "taker-found", "open-chat", "sucessful" +