diff --git a/.env-sample b/.env-sample
index 84dce51b..49249da7 100644
--- a/.env-sample
+++ b/.env-sample
@@ -30,7 +30,7 @@ POSTGRES_HOST='127.0.0.1'
POSTGRES_PORT='5432'
# Tor proxy for remote calls (e.g. fetching prices or sending Telegram messages)
-USE_TOR='True'
+USE_TOR=True
TOR_PROXY='127.0.0.1:9050'
# Auto unlock LND password. Only used in development docker-compose environment.
@@ -166,4 +166,4 @@ MINIMUM_TARGET_CONF = 24
SLASHED_BOND_REWARD_SPLIT = 0.5
# Username for HTLCs escrows
-ESCROW_USERNAME = 'admin'
+ESCROW_USERNAME = 'admin'
\ No newline at end of file
diff --git a/api/models/order.py b/api/models/order.py
index 14762134..a8964997 100644
--- a/api/models/order.py
+++ b/api/models/order.py
@@ -1,3 +1,4 @@
+# We use custom seeded UUID generation during testing
import uuid
from decouple import config
@@ -9,6 +10,19 @@ from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.utils import timezone
+if config("COORDINATOR_TESTING", cast=bool, default=False):
+ import random
+ import string
+
+ random.seed(1)
+ chars = string.ascii_lowercase + string.digits
+
+ def custom_uuid():
+ return uuid.uuid5(uuid.NAMESPACE_DNS, "".join(random.choices(chars, k=20)))
+
+else:
+ custom_uuid = uuid.uuid4
+
class Order(models.Model):
class Types(models.IntegerChoices):
@@ -44,7 +58,7 @@ class Order(models.Model):
NESINV = 4, "Neither escrow locked or invoice submitted"
# order info
- reference = models.UUIDField(default=uuid.uuid4, editable=False)
+ reference = models.UUIDField(default=custom_uuid, editable=False)
status = models.PositiveSmallIntegerField(
choices=Status.choices, null=False, default=Status.WFB
)
diff --git a/api/oas_schemas.py b/api/oas_schemas.py
index ed59bcd5..569e9658 100644
--- a/api/oas_schemas.py
+++ b/api/oas_schemas.py
@@ -5,6 +5,7 @@ from django.conf import settings
from drf_spectacular.utils import OpenApiExample, OpenApiParameter
from api.serializers import (
+ InfoSerializer,
ListOrderSerializer,
OrderDetailSerializer,
StealthSerializer,
@@ -322,17 +323,7 @@ class OrderViewSchema:
),
],
"responses": {
- 200: {
- "type": "object",
- "additionalProperties": {
- "oneOf": [
- {"type": "str"},
- {"type": "number"},
- {"type": "object"},
- {"type": "boolean"},
- ],
- },
- },
+ 200: OrderDetailSerializer,
400: {
"type": "object",
"properties": {
@@ -474,6 +465,16 @@ class RobotViewSchema:
"type": "integer",
"description": "Last order id if present",
},
+ "earned_rewards": {
+ "type": "integer",
+ "description": "Satoshis available to be claimed",
+ },
+ "last_login": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": True,
+ "description": "Last time the coordinator saw this robot",
+ },
},
},
},
@@ -517,6 +518,9 @@ class InfoViewSchema:
- on-chain swap fees
"""
),
+ "responses": {
+ 200: InfoSerializer,
+ },
}
diff --git a/api/serializers.py b/api/serializers.py
index d2b0db2a..7da7c2bc 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -6,13 +6,19 @@ from .models import MarketTick, Order
RETRY_TIME = int(config("RETRY_TIME"))
+class VersionSerializer(serializers.Serializer):
+ major = serializers.IntegerField()
+ minor = serializers.IntegerField()
+ patch = serializers.IntegerField()
+
+
class InfoSerializer(serializers.Serializer):
num_public_buy_orders = serializers.IntegerField()
num_public_sell_orders = serializers.IntegerField()
book_liquidity = serializers.IntegerField(
help_text="Total amount of BTC in the order book"
)
- active_robots_today = serializers.CharField()
+ active_robots_today = serializers.IntegerField()
last_day_nonkyc_btc_premium = serializers.FloatField(
help_text="Average premium (weighted by volume) of the orders in the last 24h"
)
@@ -23,6 +29,7 @@ class InfoSerializer(serializers.Serializer):
help_text="Total volume in BTC since exchange's inception"
)
lnd_version = serializers.CharField()
+ cln_version = serializers.CharField()
robosats_running_commit_hash = serializers.CharField()
alternative_site = serializers.CharField()
alternative_name = serializers.CharField()
@@ -35,6 +42,17 @@ class InfoSerializer(serializers.Serializer):
current_swap_fee_rate = serializers.FloatField(
help_text="Swap fees to perform on-chain transaction (percent)"
)
+ version = VersionSerializer()
+ notice_severity = serializers.ChoiceField(
+ choices=[
+ ("none", "none"),
+ ("warning", "warning"),
+ ("success", "success"),
+ ("error", "error"),
+ ("info", "info"),
+ ]
+ )
+ notice_message = serializers.CharField()
class ListOrderSerializer(serializers.ModelSerializer):
@@ -60,7 +78,7 @@ class ListOrderSerializer(serializers.ModelSerializer):
"escrow_duration",
"bond_size",
"latitude",
- "longitude"
+ "longitude",
)
@@ -160,10 +178,13 @@ class OrderDetailSerializer(serializers.ModelSerializer):
required=False,
help_text="Price of the order in the order's currency at the time of request (upto 5 significant digits)",
)
- premium = serializers.IntegerField(
+ premium = serializers.CharField(
+ required=False, help_text="Premium over the CEX price set by the maker"
+ )
+ premium_now = serializers.FloatField(
required=False, help_text="Premium over the CEX price at the current time"
)
- premium_percentile = serializers.IntegerField(
+ premium_percentile = serializers.FloatField(
required=False,
help_text="(Only if `is_maker`) Premium percentile of your order compared to other public orders in the same currency currently in the order book",
)
@@ -253,11 +274,11 @@ class OrderDetailSerializer(serializers.ModelSerializer):
required=False,
help_text="in percentage, the swap fee rate the platform charges",
)
- latitude = serializers.FloatField(
+ latitude = serializers.CharField(
required=False,
help_text="Latitude of the order for F2F payments",
)
- longitude = serializers.FloatField(
+ longitude = serializers.CharField(
required=False,
help_text="Longitude of the order for F2F payments",
)
@@ -300,7 +321,11 @@ class OrderDetailSerializer(serializers.ModelSerializer):
)
maker_summary = SummarySerializer(required=False)
taker_summary = SummarySerializer(required=False)
- platform_summary = PlatformSummarySerializer(required=True)
+ satoshis_now = serializers.IntegerField(
+ required=False,
+ help_text="Maximum size of the order right now in Satoshis",
+ )
+ platform_summary = PlatformSummarySerializer(required=False)
expiry_message = serializers.CharField(
required=False,
help_text="The reason the order expired (message associated with the `expiry_reason`)",
@@ -338,7 +363,9 @@ class OrderDetailSerializer(serializers.ModelSerializer):
"payment_method",
"is_explicit",
"premium",
+ "premium_now",
"satoshis",
+ "satoshis_now",
"maker",
"taker",
"escrow_duration",
@@ -350,7 +377,6 @@ class OrderDetailSerializer(serializers.ModelSerializer):
"maker_status",
"taker_status",
"price_now",
- "premium",
"premium_percentile",
"num_similar_orders",
"tg_enabled",
@@ -441,7 +467,7 @@ class OrderPublicSerializer(serializers.ModelSerializer):
"satoshis_now",
"bond_size",
"latitude",
- "longitude"
+ "longitude",
)
@@ -482,7 +508,7 @@ class MakeOrderSerializer(serializers.ModelSerializer):
"escrow_duration",
"bond_size",
"latitude",
- "longitude"
+ "longitude",
)
diff --git a/api/urls.py b/api/urls.py
index 407dc2d0..7d8b19c6 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -20,19 +20,20 @@ from .views import (
urlpatterns = [
path("schema/", SpectacularAPIView.as_view(), name="schema"),
path("", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
- path("make/", MakerView.as_view()),
+ path("make/", MakerView.as_view(), name="make"),
path(
"order/",
OrderView.as_view({"get": "get", "post": "take_update_confirm_dispute_cancel"}),
+ name="order",
),
- path("robot/", RobotView.as_view()),
- path("book/", BookView.as_view()),
- path("info/", InfoView.as_view()),
- path("price/", PriceView.as_view()),
- path("limits/", LimitView.as_view()),
- path("reward/", RewardView.as_view()),
- path("historical/", HistoricalView.as_view()),
- path("ticks/", TickView.as_view()),
- path("stealth/", StealthView.as_view()),
- path("chat/", ChatView.as_view({"get": "get", "post": "post"})),
+ path("robot/", RobotView.as_view(), name="robot"),
+ path("book/", BookView.as_view(), name="book"),
+ path("info/", InfoView.as_view({"get": "get"}), name="info"),
+ path("price/", PriceView.as_view(), name="price"),
+ path("limits/", LimitView.as_view(), name="limits"),
+ path("reward/", RewardView.as_view(), name="reward"),
+ path("historical/", HistoricalView.as_view(), name="historical"),
+ path("ticks/", TickView.as_view(), name="ticks"),
+ path("stealth/", StealthView.as_view(), name="stealth"),
+ path("chat/", ChatView.as_view({"get": "get", "post": "post"}), name="chat"),
]
diff --git a/api/views.py b/api/views.py
index 2239d18a..ae4bf9ac 100644
--- a/api/views.py
+++ b/api/views.py
@@ -724,7 +724,7 @@ class BookView(ListAPIView):
return Response(book_data, status=status.HTTP_200_OK)
-class InfoView(ListAPIView):
+class InfoView(viewsets.ViewSet):
serializer_class = InfoSerializer
@extend_schema(**InfoViewSchema.get)
diff --git a/docs/assets/schemas/api-v0.5.3.yaml b/docs/assets/schemas/api-v0.5.3.yaml
index 376333b4..51d53a67 100644
--- a/docs/assets/schemas/api-v0.5.3.yaml
+++ b/docs/assets/schemas/api-v0.5.3.yaml
@@ -140,7 +140,7 @@ paths:
description: ''
/api/info/:
get:
- operationId: info_list
+ operationId: info_retrieve
description: |2
Get general info (overview) about the exchange.
@@ -172,9 +172,7 @@ paths:
content:
application/json:
schema:
- type: array
- items:
- $ref: '#/components/schemas/Info'
+ $ref: '#/components/schemas/Info'
description: ''
/api/limits/:
get:
@@ -563,13 +561,7 @@ paths:
content:
application/json:
schema:
- type: object
- additionalProperties:
- oneOf:
- - type: str
- - type: number
- - type: object
- - type: boolean
+ $ref: '#/components/schemas/OrderDetail'
description: ''
'400':
content:
@@ -1103,7 +1095,7 @@ components:
type: integer
description: Total amount of BTC in the order book
active_robots_today:
- type: string
+ type: integer
last_day_nonkyc_btc_premium:
type: number
format: double
@@ -1119,6 +1111,8 @@ components:
description: Total volume in BTC since exchange's inception
lnd_version:
type: string
+ cln_version:
+ type: string
robosats_running_commit_hash:
type: string
alternative_site:
@@ -1147,12 +1141,19 @@ components:
type: number
format: double
description: Swap fees to perform on-chain transaction (percent)
+ version:
+ $ref: '#/components/schemas/Version'
+ notice_severity:
+ $ref: '#/components/schemas/NoticeSeverityEnum'
+ notice_message:
+ type: string
required:
- active_robots_today
- alternative_name
- alternative_site
- bond_size
- book_liquidity
+ - cln_version
- current_swap_fee_rate
- last_day_nonkyc_btc_premium
- last_day_volume
@@ -1162,10 +1163,13 @@ components:
- network
- node_alias
- node_id
+ - notice_message
+ - notice_severity
- num_public_buy_orders
- num_public_sell_orders
- robosats_running_commit_hash
- taker_fee
+ - version
ListOrder:
type: object
properties:
@@ -1355,6 +1359,20 @@ components:
required:
- currency
- id
+ NoticeSeverityEnum:
+ enum:
+ - none
+ - warning
+ - success
+ - error
+ - info
+ type: string
+ description: |-
+ * `none` - none
+ * `warning` - warning
+ * `success` - success
+ * `error` - error
+ * `info` - info
NullEnum:
enum:
- null
@@ -1952,6 +1970,19 @@ components:
nullable: true
required:
- action
+ Version:
+ type: object
+ properties:
+ major:
+ type: integer
+ minor:
+ type: integer
+ patch:
+ type: integer
+ required:
+ - major
+ - minor
+ - patch
securitySchemes:
tokenAuth:
type: apiKey
diff --git a/robosats/settings.py b/robosats/settings.py
index 492ac9cb..e497364a 100644
--- a/robosats/settings.py
+++ b/robosats/settings.py
@@ -275,9 +275,9 @@ MAX_PUBLIC_ORDER_DURATION = 24
MIN_PUBLIC_ORDER_DURATION = 0.166
# Bond size as percentage (%)
-DEFAULT_BOND_SIZE = 3
-MIN_BOND_SIZE = 2
-MAX_BOND_SIZE = 15
+DEFAULT_BOND_SIZE = float(3)
+MIN_BOND_SIZE = float(2)
+MAX_BOND_SIZE = float(15)
# Default time to provide a valid invoice and the trade escrow MINUTES
INVOICE_AND_ESCROW_DURATION = 180
diff --git a/tests/api_specs.yaml b/tests/api_specs.yaml
new file mode 100644
index 00000000..b2768cd1
--- /dev/null
+++ b/tests/api_specs.yaml
@@ -0,0 +1,2004 @@
+openapi: 3.0.3
+info:
+ title: RoboSats REST API
+ version: 0.5.3
+ x-logo:
+ url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png
+ backgroundColor: '#FFFFFF'
+ altText: RoboSats logo
+ description: |2+
+
+ REST API Documentation for [RoboSats](https://learn.robosats.com) - A Simple and Private LN P2P Exchange
+
+
+ Note:
+ The RoboSats REST API is on v0, which in other words, is beta.
+ We recommend that if you don't have time to actively maintain
+ your project, do not build it with v0 of the API. A refactored, simpler
+ and more stable version - v1 will be released soon™.
+
+
+paths:
+ /api/book/:
+ get:
+ operationId: book_list
+ description: Get public orders in the book.
+ summary: Get public orders
+ parameters:
+ - in: query
+ name: currency
+ schema:
+ type: integer
+ description: The currency id to filter by. Currency IDs can be found [here](https://github.com/RoboSats/robosats/blob/main/frontend/static/assets/currencies.json).
+ Value of `0` means ANY currency
+ - in: query
+ name: type
+ schema:
+ type: integer
+ enum:
+ - 0
+ - 1
+ - 2
+ description: |-
+ Order type to filter by
+ - `0` - BUY
+ - `1` - SELL
+ - `2` - ALL
+ tags:
+ - book
+ security:
+ - tokenAuth: []
+ - {}
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/OrderPublic'
+ description: ''
+ /api/chat/:
+ get:
+ operationId: chat_retrieve
+ description: Returns chat messages for an order with an index higher than `offset`.
+ tags:
+ - chat
+ security:
+ - tokenAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PostMessage'
+ description: ''
+ post:
+ operationId: chat_create
+ description: Adds one new message to the chatroom.
+ tags:
+ - chat
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PostMessage'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/PostMessage'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/PostMessage'
+ required: true
+ security:
+ - tokenAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PostMessage'
+ description: ''
+ /api/historical/:
+ get:
+ operationId: historical_list
+ description: Get historical exchange activity. Currently, it lists each day's
+ total contracts and their volume in BTC since inception.
+ summary: Get historical exchange activity
+ tags:
+ - historical
+ security:
+ - tokenAuth: []
+ - {}
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ additionalProperties:
+ type: object
+ properties:
+ volume:
+ type: integer
+ description: Total Volume traded on that particular date
+ num_contracts:
+ type: number
+ description: Number of successful trades on that particular
+ date
+ examples:
+ TruncatedExample:
+ value:
+ - :
+ code: USD
+ price: '42069.69'
+ min_amount: '4.2'
+ max_amount: '420.69'
+ summary: Truncated example
+ description: ''
+ /api/info/:
+ get:
+ operationId: info_retrieve
+ description: |2
+
+ Get general info (overview) about the exchange.
+
+ **Info**:
+ - Current market data
+ - num. of orders
+ - book liquidity
+ - 24h active robots
+ - 24h non-KYC premium
+ - 24h volume
+ - all time volume
+ - Node info
+ - lnd version
+ - node id
+ - node alias
+ - network
+ - Fees
+ - maker and taker fees
+ - on-chain swap fees
+ summary: Get info
+ tags:
+ - info
+ security:
+ - tokenAuth: []
+ - {}
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Info'
+ description: ''
+ /api/limits/:
+ get:
+ operationId: limits_list
+ description: Get a list of order limits for every currency pair available.
+ summary: List order limits
+ tags:
+ - limits
+ security:
+ - tokenAuth: []
+ - {}
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ additionalProperties:
+ type: object
+ properties:
+ code:
+ type: string
+ description: Three letter currency symbol
+ price:
+ type: integer
+ min_amount:
+ type: integer
+ description: Minimum amount allowed in an order in the particular
+ currency
+ max_amount:
+ type: integer
+ description: Maximum amount allowed in an order in the particular
+ currency
+ examples:
+ TruncatedExample.RealResponseContainsAllTheCurrencies:
+ value:
+ - :
+ code: USD
+ price: '42069.69'
+ min_amount: '4.2'
+ max_amount: '420.69'
+ summary: Truncated example. Real response contains all the currencies
+ description: ''
+ /api/make/:
+ post:
+ operationId: make_create
+ description: |2
+
+ Create a new order as a maker.
+
+
+ Default values for the following fields if not specified:
+ - `public_duration` - **24**
+ - `escrow_duration` - **180**
+ - `bond_size` - **3.0**
+ - `has_range` - **false**
+ - `premium` - **0**
+ summary: Create a maker order
+ tags:
+ - make
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/MakeOrder'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/MakeOrder'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/MakeOrder'
+ required: true
+ security:
+ - tokenAuth: []
+ responses:
+ '201':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ListOrder'
+ description: ''
+ '400':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ bad_request:
+ type: string
+ description: Reason for the failure
+ description: ''
+ '409':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ bad_request:
+ type: string
+ description: Reason for the failure
+ description: ''
+ /api/order/:
+ get:
+ operationId: order_retrieve
+ description: |2+
+
+ Get the order details. Details include/exclude attributes according to what is the status of the order
+
+ The following fields are available irrespective of whether you are a participant or not (A participant is either a taker or a maker of an order)
+ All the other fields are only available when you are either the taker or the maker of the order:
+
+ - `id`
+ - `status`
+ - `created_at`
+ - `expires_at`
+ - `type`
+ - `currency`
+ - `amount`
+ - `has_range`
+ - `min_amount`
+ - `max_amount`
+ - `payment_method`
+ - `is_explicit`
+ - `premium`
+ - `satoshis`
+ - `maker`
+ - `taker`
+ - `escrow_duration`
+ - `total_secs_exp`
+ - `penalty`
+ - `is_maker`
+ - `is_taker`
+ - `is_participant`
+ - `maker_status`
+ - `taker_status`
+ - `price_now`
+
+ ### Order Status
+
+ The response of this route changes according to the status of the order. Some fields are documented below (check the 'Responses' section)
+ with the status code of when they are available and some or not. With v1 API we aim to simplify this
+ route to make it easier to understand which fields are available on which order status codes.
+
+ `status` specifies the status of the order. Below is a list of possible values (status codes) and what they mean:
+ - `0` "Waiting for maker bond"
+ - `1` "Public"
+ - `2` "Paused"
+ - `3` "Waiting for taker bond"
+ - `4` "Cancelled"
+ - `5` "Expired"
+ - `6` "Waiting for trade collateral and buyer invoice"
+ - `7` "Waiting only for seller trade collateral"
+ - `8` "Waiting only for buyer invoice"
+ - `9` "Sending fiat - In chatroom"
+ - `10` "Fiat sent - In chatroom"
+ - `11` "In dispute"
+ - `12` "Collaboratively cancelled"
+ - `13` "Sending satoshis to buyer"
+ - `14` "Sucessful trade"
+ - `15` "Failed lightning network routing"
+ - `16` "Wait for dispute resolution"
+ - `17` "Maker lost dispute"
+ - `18` "Taker lost dispute"
+
+
+ Notes:
+ - both `price_now` and `premium_now` are always calculated irrespective of whether `is_explicit` = true or false
+
+ summary: Get order details
+ parameters:
+ - in: query
+ name: order_id
+ schema:
+ type: integer
+ required: true
+ tags:
+ - order
+ security:
+ - tokenAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OrderDetail'
+ description: ''
+ '400':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ bad_request:
+ type: string
+ description: Reason for the failure
+ examples:
+ OrderCancelled:
+ value:
+ bad_request: This order has been cancelled collaborativelly
+ summary: Order cancelled
+ WhenTheOrderIsNotPublicAndYouNeitherTheTakerNorMaker:
+ value:
+ bad_request: This order is not available
+ summary: When the order is not public and you neither the taker
+ nor maker
+ WhenMakerBondExpires(asMaker):
+ value:
+ bad_request: Invoice expired. You did not confirm publishing the
+ order in time. Make a new order.
+ summary: When maker bond expires (as maker)
+ WhenRobosatsNodeIsDown:
+ value:
+ bad_request: The Lightning Network Daemon (LND) is down. Write
+ in the Telegram group to make sure the staff is aware.
+ summary: When Robosats node is down
+ description: ''
+ '403':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ bad_request:
+ type: string
+ description: Reason for the failure
+ default: This order is not available
+ description: ''
+ '404':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ bad_request:
+ type: string
+ description: Reason for the failure
+ default: Invalid order Id
+ description: ''
+ post:
+ operationId: order_create
+ description: |2+
+
+ Update an order
+
+ `action` field is required and determines what is to be done. Below
+ is an explanation of what each action does:
+
+ - `take`
+ - If the order has not expired and is still public, on a
+ successful take, you get the same response as if `GET /order`
+ was called and the status of the order was `3` (waiting for
+ taker bond) which means `bond_satoshis` and `bond_invoice` are
+ present in the response as well. Once the `bond_invoice` is
+ paid, you successfully become the taker of the order and the
+ status of the order changes.
+ - `pause`
+ - Toggle the status of an order from `1` to `2` and vice versa. Allowed only if status is `1` (Public) or `2` (Paused)
+ - `update_invoice`
+ - This action only is valid if you are the buyer. The `invoice`
+ field needs to be present in the body and the value must be a
+ valid LN invoice as cleartext PGP message signed with the robot key. Make sure to perform this action only when
+ both the bonds are locked. i.e The status of your order is
+ at least `6` (Waiting for trade collateral and buyer invoice)
+ - `update_address`
+ - This action is only valid if you are the buyer. This action is
+ used to set an on-chain payout address if you wish to have your
+ payout be received on-chain. Only valid if there is an address in the body as
+ cleartext PGP message signed with the robot key. This enables on-chain swap for the
+ order, so even if you earlier had submitted a LN invoice, it
+ will be ignored. You get to choose the `mining_fee_rate` as
+ well. Mining fee rate is specified in sats/vbyte.
+ - `cancel`
+ - This action is used to cancel an existing order. You cannot cancel an order if it's in one of the following states:
+ - `1` - Cancelled
+ - `5` - Expired
+ - `11` - In dispute
+ - `12` - Collaboratively cancelled
+ - `13` - Sending satoshis to buyer
+ - `14` - Successful trade
+ - `15` - Failed lightning network routing
+ - `17` - Maker lost dispute
+ - `18` - Taker lost dispute
+
+ Note that there are penalties involved for cancelling a order
+ mid-trade so use this action carefully:
+
+ - As a maker if you cancel an order after you have locked your
+ maker bond, you are returned your bond. This may change in
+ the future to prevent DDoSing the LN node and you won't be
+ returned the maker bond.
+ - As a taker there is a time penalty involved if you `take` an
+ order and cancel it without locking the taker bond.
+ - For both taker or maker, if you cancel the order when both
+ have locked their bonds (status = `6` or `7`), you loose your
+ bond and a percent of it goes as "rewards" to your
+ counterparty and some of it the platform keeps. This is to
+ discourage wasting time and DDoSing the platform.
+ - For both taker or maker, if you cancel the order when the
+ escrow is locked (status = `8` or `9`), you trigger a
+ collaborative cancel request. This sets
+ `(m|t)aker_asked_cancel` field to `true` depending on whether
+ you are the maker or the taker respectively, so that your
+ counterparty is informed that you asked for a cancel.
+ - For both taker or maker, and your counterparty asked for a
+ cancel (i.e `(m|t)aker_asked_cancel` is true), and you cancel
+ as well, a collaborative cancel takes place which returns
+ both the bonds and escrow to the respective parties. Note
+ that in the future there will be a cost for even
+ collaborativelly cancelling orders for both parties.
+ - `confirm`
+ - This is a **crucial** action. This confirms the sending and
+ receiving of fiat depending on whether you are a buyer or
+ seller. There is not much RoboSats can do to actually confirm
+ and verify the fiat payment channel. It is up to you to make
+ sure of the correct amount was received before you confirm.
+ This action is only allowed when status is either `9` (Sending
+ fiat - In chatroom) or `10` (Fiat sent - In chatroom)
+ - If you are the buyer, it simply sets `fiat_sent` to `true`
+ which means that you have sent the fiat using the payment
+ method selected by the seller and signals the seller that the
+ fiat payment was done.
+ - If you are the seller, be very careful and double check
+ before performing this action. Check that your fiat payment
+ method was successful in receiving the funds and whether it
+ was the correct amount. This action settles the escrow and
+ pays the buyer and sets the the order status to `13` (Sending
+ satohis to buyer) and eventually to `14` (successful trade).
+ - `undo_confirm`
+ - This action will undo the fiat_sent confirmation by the buyer
+ it is allowed only once the fiat is confirmed as sent and can
+ enable the collaborative cancellation option if an off-robosats
+ payment cannot be completed or is blocked.
+ - `dispute`
+ - This action is allowed only if status is `9` or `10`. It sets
+ the order status to `11` (In dispute) and sets `is_disputed` to
+ `true`. Both the bonds and the escrow are settled (i.e RoboSats
+ takes custody of the funds). Disputes can take long to resolve,
+ it might trigger force closure for unresolved HTLCs). Dispute
+ winner will have to submit a new invoice for value of escrow +
+ bond.
+ - `submit_statement`
+ - This action updates the dispute statement. Allowed only when
+ status is `11` (In dispute). `statement` must be sent in the
+ request body and should be a string. 100 chars < length of
+ `statement` < 5000 chars. You need to describe the reason for
+ raising a dispute. The `(m|t)aker_statement` field is set
+ respectively. Only when both parties have submitted their
+ dispute statement, the order status changes to `16` (Waiting
+ for dispute resolution)
+ - `rate_platform`
+ - Let us know how much you love (or hate 😢) RoboSats.
+ You can rate the platform from `1-5` using the `rate` field in the request body
+
+ summary: Update order
+ parameters:
+ - in: query
+ name: order_id
+ schema:
+ type: integer
+ required: true
+ tags:
+ - order
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateOrder'
+ examples:
+ UserNotAuthenticated:
+ value:
+ bad_request: Woops! It seems you do not have a robot avatar
+ summary: User not authenticated
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/UpdateOrder'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/UpdateOrder'
+ required: true
+ security:
+ - tokenAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OrderDetail'
+ description: ''
+ '400':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ bad_request:
+ type: string
+ description: Reason for the failure
+ examples:
+ UserNotAuthenticated:
+ value:
+ bad_request: Woops! It seems you do not have a robot avatar
+ summary: User not authenticated
+ description: ''
+ /api/price/:
+ get:
+ operationId: price_list
+ description: Get the last market price for each currency. Also, returns some
+ more info about the last trade in each currency.
+ summary: Get last market prices
+ tags:
+ - price
+ security:
+ - tokenAuth: []
+ - {}
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ additionalProperties:
+ type: object
+ properties:
+ price:
+ type: integer
+ volume:
+ type: integer
+ premium:
+ type: integer
+ timestamp:
+ type: string
+ format: date-time
+ examples:
+ TruncatedExample.RealResponseContainsAllTheCurrencies:
+ value:
+ - :
+ price: 21948.89
+ volume: 0.01366812
+ premium: 3.5
+ timestamp: '2022-09-13T14:32:40.591774Z'
+ summary: Truncated example. Real response contains all the currencies
+ description: ''
+ /api/reward/:
+ post:
+ operationId: reward_create
+ description: Withdraw user reward by submitting an invoice. The invoice must
+ be send as cleartext PGP message signed with the robot key
+ summary: Withdraw reward
+ tags:
+ - reward
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ClaimReward'
+ examples:
+ UserNotAuthenticated:
+ value:
+ bad_request: Woops! It seems you do not have a robot avatar
+ summary: User not authenticated
+ WhenNoRewardsEarned:
+ value:
+ successful_withdrawal: false
+ bad_invoice: You have not earned rewards
+ summary: When no rewards earned
+ BadInvoiceOrInCaseOfPaymentFailure:
+ value:
+ successful_withdrawal: false
+ bad_invoice: Does not look like a valid lightning invoice
+ summary: Bad invoice or in case of payment failure
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/ClaimReward'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/ClaimReward'
+ security:
+ - tokenAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successful_withdrawal:
+ type: boolean
+ default: true
+ description: ''
+ '400':
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - type: object
+ properties:
+ successful_withdrawal:
+ type: boolean
+ default: false
+ bad_invoice:
+ type: string
+ description: More context for the reason of the failure
+ - type: object
+ properties:
+ successful_withdrawal:
+ type: boolean
+ default: false
+ bad_request:
+ type: string
+ description: More context for the reason of the failure
+ examples:
+ UserNotAuthenticated:
+ value:
+ bad_request: Woops! It seems you do not have a robot avatar
+ summary: User not authenticated
+ WhenNoRewardsEarned:
+ value:
+ successful_withdrawal: false
+ bad_invoice: You have not earned rewards
+ summary: When no rewards earned
+ BadInvoiceOrInCaseOfPaymentFailure:
+ value:
+ successful_withdrawal: false
+ bad_invoice: Does not look like a valid lightning invoice
+ summary: Bad invoice or in case of payment failure
+ description: ''
+ /api/robot/:
+ get:
+ operationId: robot_retrieve
+ description: |2+
+
+ Get robot info 🤖
+
+ An authenticated request (has the token's sha256 hash encoded as base 91 in the Authorization header) will be
+ returned the information about the state of a robot.
+
+ Make sure you generate your token using cryptographically secure methods. [Here's]() the function the Javascript
+ client uses to generate the tokens. Since the server only receives the hash of the
+ token, it is responsibility of the client to create a strong token. Check
+ [here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.js)
+ to see how the Javascript client creates a random strong token and how it validates entropy is optimal for tokens
+ created by the user at will.
+
+ `public_key` - PGP key associated with the user (Armored ASCII format)
+ `encrypted_private_key` - Private PGP key. This is only stored on the backend for later fetching by
+ the frontend and the key can't really be used by the server since it's protected by the token
+ that only the client knows. Will be made an optional parameter in a future release.
+ On the Javascript client, It's passphrase is set to be the secret token generated.
+
+ A gpg key can be created by:
+
+ ```shell
+ gpg --full-gen-key
+ ```
+
+ it's public key can be exported in ascii armored format with:
+
+ ```shell
+ gpg --export --armor
+ ```
+
+ and it's private key can be exported in ascii armored format with:
+
+ ```shell
+ gpg --export-secret-keys --armor
+ ```
+
+ summary: Get robot info
+ tags:
+ - robot
+ security:
+ - tokenAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ encrypted_private_key:
+ type: string
+ description: Armored ASCII PGP private key block
+ nickname:
+ type: string
+ description: Username generated (Robot name)
+ public_key:
+ type: string
+ description: Armored ASCII PGP public key block
+ wants_stealth:
+ type: boolean
+ default: false
+ description: Whether the user prefers stealth invoices
+ found:
+ type: boolean
+ description: Robot had been created in the past. Only if the robot
+ was created +5 mins ago.
+ tg_enabled:
+ type: boolean
+ description: The robot has telegram notifications enabled
+ tg_token:
+ type: string
+ description: Token to enable telegram with /start
+ tg_bot_name:
+ type: string
+ description: Name of the coordinator's telegram bot
+ active_order_id:
+ type: integer
+ description: Active order id if present
+ last_order_id:
+ type: integer
+ description: Last order id if present
+ earned_rewards:
+ type: integer
+ description: Satoshis available to be claimed
+ last_login:
+ type: string
+ format: date-time
+ nullable: true
+ description: Last time the coordinator saw this robot
+ examples:
+ SuccessfullyRetrievedRobot:
+ value:
+ nickname: SatoshiNakamoto21
+ public_key: |-
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+ ......
+ ......
+ encrypted_private_key: |-
+ -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+ ......
+ ......
+ wants_stealth: true
+ summary: Successfully retrieved robot
+ description: ''
+ /api/stealth/:
+ post:
+ operationId: stealth_create
+ description: Update stealth invoice option for the user
+ summary: Update stealth option
+ tags:
+ - stealth
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Stealth'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/Stealth'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/Stealth'
+ required: true
+ security:
+ - tokenAuth: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Stealth'
+ description: ''
+ '400':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ bad_request:
+ type: string
+ description: Reason for the failure
+ description: ''
+ /api/ticks/:
+ get:
+ operationId: ticks_list
+ description: |-
+ Get all market ticks. Returns a list of all the market ticks since inception.
+ CEX price is also recorded for useful insight on the historical premium of Non-KYC BTC. Price is set when taker bond is locked.
+ summary: Get market ticks
+ parameters:
+ - in: query
+ name: end
+ schema:
+ type: string
+ description: End date formatted as DD-MM-YYYY
+ - in: query
+ name: start
+ schema:
+ type: string
+ description: Start date formatted as DD-MM-YYYY
+ tags:
+ - ticks
+ security:
+ - tokenAuth: []
+ - {}
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Tick'
+ description: ''
+components:
+ schemas:
+ ActionEnum:
+ enum:
+ - pause
+ - take
+ - update_invoice
+ - update_address
+ - submit_statement
+ - dispute
+ - cancel
+ - confirm
+ - undo_confirm
+ - rate_platform
+ type: string
+ description: |-
+ * `pause` - pause
+ * `take` - take
+ * `update_invoice` - update_invoice
+ * `update_address` - update_address
+ * `submit_statement` - submit_statement
+ * `dispute` - dispute
+ * `cancel` - cancel
+ * `confirm` - confirm
+ * `undo_confirm` - undo_confirm
+ * `rate_platform` - rate_platform
+ BlankEnum:
+ enum:
+ - ''
+ ClaimReward:
+ type: object
+ properties:
+ invoice:
+ type: string
+ nullable: true
+ description: A valid LN invoice with the reward amount to withdraw
+ maxLength: 2000
+ CurrencyEnum:
+ enum:
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+ - 6
+ - 7
+ - 8
+ - 9
+ - 10
+ - 11
+ - 12
+ - 13
+ - 14
+ - 15
+ - 16
+ - 17
+ - 18
+ - 19
+ - 20
+ - 21
+ - 22
+ - 23
+ - 24
+ - 25
+ - 26
+ - 27
+ - 28
+ - 29
+ - 30
+ - 31
+ - 32
+ - 33
+ - 34
+ - 35
+ - 36
+ - 37
+ - 38
+ - 39
+ - 40
+ - 41
+ - 42
+ - 43
+ - 44
+ - 45
+ - 46
+ - 47
+ - 48
+ - 49
+ - 50
+ - 51
+ - 52
+ - 53
+ - 54
+ - 55
+ - 56
+ - 57
+ - 58
+ - 59
+ - 60
+ - 61
+ - 62
+ - 63
+ - 64
+ - 65
+ - 66
+ - 67
+ - 68
+ - 69
+ - 70
+ - 71
+ - 72
+ - 73
+ - 74
+ - 75
+ - 300
+ - 1000
+ type: integer
+ description: |-
+ * `1` - USD
+ * `2` - EUR
+ * `3` - JPY
+ * `4` - GBP
+ * `5` - AUD
+ * `6` - CAD
+ * `7` - CHF
+ * `8` - CNY
+ * `9` - HKD
+ * `10` - NZD
+ * `11` - SEK
+ * `12` - KRW
+ * `13` - SGD
+ * `14` - NOK
+ * `15` - MXN
+ * `16` - BYN
+ * `17` - RUB
+ * `18` - ZAR
+ * `19` - TRY
+ * `20` - BRL
+ * `21` - CLP
+ * `22` - CZK
+ * `23` - DKK
+ * `24` - HRK
+ * `25` - HUF
+ * `26` - INR
+ * `27` - ISK
+ * `28` - PLN
+ * `29` - RON
+ * `30` - ARS
+ * `31` - VES
+ * `32` - COP
+ * `33` - PEN
+ * `34` - UYU
+ * `35` - PYG
+ * `36` - BOB
+ * `37` - IDR
+ * `38` - ANG
+ * `39` - CRC
+ * `40` - CUP
+ * `41` - DOP
+ * `42` - GHS
+ * `43` - GTQ
+ * `44` - ILS
+ * `45` - JMD
+ * `46` - KES
+ * `47` - KZT
+ * `48` - MYR
+ * `49` - NAD
+ * `50` - NGN
+ * `51` - AZN
+ * `52` - PAB
+ * `53` - PHP
+ * `54` - PKR
+ * `55` - QAR
+ * `56` - SAR
+ * `57` - THB
+ * `58` - TTD
+ * `59` - VND
+ * `60` - XOF
+ * `61` - TWD
+ * `62` - TZS
+ * `63` - XAF
+ * `64` - UAH
+ * `65` - EGP
+ * `66` - LKR
+ * `67` - MAD
+ * `68` - AED
+ * `69` - TND
+ * `70` - ETB
+ * `71` - GEL
+ * `72` - UGX
+ * `73` - RSD
+ * `74` - IRT
+ * `75` - BDT
+ * `300` - XAU
+ * `1000` - BTC
+ ExpiryReasonEnum:
+ enum:
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+ type: integer
+ description: |-
+ * `0` - Expired not taken
+ * `1` - Maker bond not locked
+ * `2` - Escrow not locked
+ * `3` - Invoice not submitted
+ * `4` - Neither escrow locked or invoice submitted
+ Info:
+ type: object
+ properties:
+ num_public_buy_orders:
+ type: integer
+ num_public_sell_orders:
+ type: integer
+ book_liquidity:
+ type: integer
+ description: Total amount of BTC in the order book
+ active_robots_today:
+ type: integer
+ last_day_nonkyc_btc_premium:
+ type: number
+ format: double
+ description: Average premium (weighted by volume) of the orders in the last
+ 24h
+ last_day_volume:
+ type: number
+ format: double
+ description: Total volume in BTC in the last 24h
+ lifetime_volume:
+ type: number
+ format: double
+ description: Total volume in BTC since exchange's inception
+ lnd_version:
+ type: string
+ cln_version:
+ type: string
+ robosats_running_commit_hash:
+ type: string
+ alternative_site:
+ type: string
+ alternative_name:
+ type: string
+ node_alias:
+ type: string
+ node_id:
+ type: string
+ network:
+ type: string
+ maker_fee:
+ type: number
+ format: double
+ description: Exchange's set maker fee
+ taker_fee:
+ type: number
+ format: double
+ description: 'Exchange''s set taker fee '
+ bond_size:
+ type: number
+ format: double
+ description: Default bond size (percent)
+ current_swap_fee_rate:
+ type: number
+ format: double
+ description: Swap fees to perform on-chain transaction (percent)
+ version:
+ $ref: '#/components/schemas/Version'
+ notice_severity:
+ $ref: '#/components/schemas/NoticeSeverityEnum'
+ notice_message:
+ type: string
+ required:
+ - active_robots_today
+ - alternative_name
+ - alternative_site
+ - bond_size
+ - book_liquidity
+ - cln_version
+ - current_swap_fee_rate
+ - last_day_nonkyc_btc_premium
+ - last_day_volume
+ - lifetime_volume
+ - lnd_version
+ - maker_fee
+ - network
+ - node_alias
+ - node_id
+ - notice_message
+ - notice_severity
+ - num_public_buy_orders
+ - num_public_sell_orders
+ - robosats_running_commit_hash
+ - taker_fee
+ - version
+ ListOrder:
+ type: object
+ properties:
+ id:
+ type: integer
+ readOnly: true
+ status:
+ allOf:
+ - $ref: '#/components/schemas/StatusEnum'
+ minimum: 0
+ maximum: 32767
+ created_at:
+ type: string
+ format: date-time
+ expires_at:
+ type: string
+ format: date-time
+ type:
+ allOf:
+ - $ref: '#/components/schemas/TypeEnum'
+ minimum: 0
+ maximum: 32767
+ currency:
+ type: integer
+ nullable: true
+ amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ has_range:
+ type: boolean
+ min_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ max_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ payment_method:
+ type: string
+ maxLength: 70
+ is_explicit:
+ type: boolean
+ premium:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,2})?$
+ nullable: true
+ satoshis:
+ type: integer
+ maximum: 5000000
+ minimum: 20000
+ nullable: true
+ maker:
+ type: integer
+ nullable: true
+ taker:
+ type: integer
+ nullable: true
+ escrow_duration:
+ type: integer
+ maximum: 28800
+ minimum: 1800
+ bond_size:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
+ latitude:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,2}(?:\.\d{0,6})?$
+ nullable: true
+ longitude:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,6})?$
+ nullable: true
+ required:
+ - expires_at
+ - id
+ - type
+ MakeOrder:
+ type: object
+ properties:
+ type:
+ allOf:
+ - $ref: '#/components/schemas/TypeEnum'
+ minimum: 0
+ maximum: 32767
+ currency:
+ type: integer
+ description: Currency id. See [here](https://github.com/RoboSats/robosats/blob/main/frontend/static/assets/currencies.json)
+ for a list of all IDs
+ amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ has_range:
+ type: boolean
+ default: false
+ description: |-
+ Whether the order specifies a range of amount or a fixed amount.
+
+ If `true`, then `min_amount` and `max_amount` fields are **required**.
+
+ If `false` then `amount` is **required**
+ min_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ max_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ payment_method:
+ type: string
+ default: not specified
+ description: Can be any string. The UI recognizes [these payment methods](https://github.com/RoboSats/robosats/blob/main/frontend/src/components/payment-methods/Methods.js)
+ and displays them with a logo.
+ maxLength: 70
+ is_explicit:
+ type: boolean
+ default: false
+ description: Whether the order is explicitly priced or not. If set to `true`
+ then `satoshis` need to be specified
+ premium:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,2})?$
+ nullable: true
+ satoshis:
+ type: integer
+ maximum: 5000000
+ minimum: 20000
+ nullable: true
+ public_duration:
+ type: integer
+ maximum: 86400
+ minimum: 597.6
+ escrow_duration:
+ type: integer
+ maximum: 28800
+ minimum: 1800
+ bond_size:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
+ latitude:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,2}(?:\.\d{0,6})?$
+ nullable: true
+ longitude:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,6})?$
+ nullable: true
+ required:
+ - currency
+ - type
+ Nested:
+ type: object
+ properties:
+ id:
+ type: integer
+ readOnly: true
+ currency:
+ allOf:
+ - $ref: '#/components/schemas/CurrencyEnum'
+ minimum: 0
+ maximum: 32767
+ exchange_rate:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,14}(?:\.\d{0,4})?$
+ nullable: true
+ timestamp:
+ type: string
+ format: date-time
+ required:
+ - currency
+ - id
+ NoticeSeverityEnum:
+ enum:
+ - none
+ - warning
+ - success
+ - error
+ - info
+ type: string
+ description: |-
+ * `none` - none
+ * `warning` - warning
+ * `success` - success
+ * `error` - error
+ * `info` - info
+ NullEnum:
+ enum:
+ - null
+ OrderDetail:
+ type: object
+ properties:
+ id:
+ type: integer
+ readOnly: true
+ status:
+ allOf:
+ - $ref: '#/components/schemas/StatusEnum'
+ minimum: 0
+ maximum: 32767
+ created_at:
+ type: string
+ format: date-time
+ expires_at:
+ type: string
+ format: date-time
+ type:
+ allOf:
+ - $ref: '#/components/schemas/TypeEnum'
+ minimum: 0
+ maximum: 32767
+ currency:
+ type: integer
+ nullable: true
+ amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ has_range:
+ type: boolean
+ min_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ max_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ payment_method:
+ type: string
+ maxLength: 70
+ is_explicit:
+ type: boolean
+ premium:
+ type: string
+ description: Premium over the CEX price set by the maker
+ premium_now:
+ type: number
+ format: double
+ description: Premium over the CEX price at the current time
+ satoshis:
+ type: integer
+ maximum: 5000000
+ minimum: 20000
+ nullable: true
+ satoshis_now:
+ type: integer
+ description: Maximum size of the order right now in Satoshis
+ maker:
+ type: integer
+ nullable: true
+ taker:
+ type: integer
+ nullable: true
+ escrow_duration:
+ type: integer
+ maximum: 28800
+ minimum: 1800
+ total_secs_exp:
+ type: integer
+ description: Duration of time (in seconds) to expire, according to the current
+ status of order.This is duration of time after `created_at` (in seconds)
+ that the order will automatically expire.This value changes according
+ to which stage the order is in
+ penalty:
+ type: string
+ format: date-time
+ description: Time when the user penalty will expire. Penalty applies when
+ you create orders repeatedly without commiting a bond
+ is_maker:
+ type: boolean
+ description: Whether you are the maker or not
+ is_taker:
+ type: boolean
+ description: Whether you are the taker or not
+ is_participant:
+ type: boolean
+ description: True if you are either a taker or maker, False otherwise
+ maker_status:
+ type: string
+ description: |-
+ Status of the maker:
+ - **'Active'** (seen within last 2 min)
+ - **'Seen Recently'** (seen within last 10 min)
+ - **'Inactive'** (seen more than 10 min ago)
+
+ Note: When you make a request to this route, your own status get's updated and can be seen by your counterparty
+ taker_status:
+ type: boolean
+ description: True if you are either a taker or maker, False otherwise
+ price_now:
+ type: integer
+ description: Price of the order in the order's currency at the time of request
+ (upto 5 significant digits)
+ premium_percentile:
+ type: number
+ format: double
+ description: (Only if `is_maker`) Premium percentile of your order compared
+ to other public orders in the same currency currently in the order book
+ num_similar_orders:
+ type: integer
+ description: (Only if `is_maker`) The number of public orders of the same
+ currency currently in the order book
+ tg_enabled:
+ type: boolean
+ description: (Only if `is_maker`) Whether Telegram notification is enabled
+ or not
+ tg_token:
+ type: string
+ description: (Only if `is_maker`) Your telegram bot token required to enable
+ notifications.
+ tg_bot_name:
+ type: string
+ description: (Only if `is_maker`) The Telegram username of the bot
+ is_buyer:
+ type: boolean
+ description: Whether you are a buyer of sats (you will be receiving sats)
+ is_seller:
+ type: boolean
+ description: Whether you are a seller of sats or not (you will be sending
+ sats)
+ maker_nick:
+ type: string
+ description: Nickname (Robot name) of the maker
+ taker_nick:
+ type: string
+ description: Nickname (Robot name) of the taker
+ status_message:
+ type: string
+ description: The current status of the order corresponding to the `status`
+ is_fiat_sent:
+ type: boolean
+ description: Whether or not the fiat amount is sent by the buyer
+ is_disputed:
+ type: boolean
+ description: Whether or not the counterparty raised a dispute
+ ur_nick:
+ type: string
+ description: Your Nick
+ maker_locked:
+ type: boolean
+ description: True if maker bond is locked, False otherwise
+ taker_locked:
+ type: boolean
+ description: True if taker bond is locked, False otherwise
+ escrow_locked:
+ type: boolean
+ description: True if escrow is locked, False otherwise. Escrow is the sats
+ to be sold, held by Robosats until the trade is finised.
+ trade_satoshis:
+ type: integer
+ description: 'Seller sees the amount of sats they need to send. Buyer sees
+ the amount of sats they will receive '
+ bond_invoice:
+ type: string
+ description: When `status` = `0`, `3`. Bond invoice to be paid
+ bond_satoshis:
+ type: integer
+ description: The bond amount in satoshis
+ escrow_invoice:
+ type: string
+ description: For the seller, the escrow invoice to be held by RoboSats
+ escrow_satoshis:
+ type: integer
+ description: The escrow amount in satoshis
+ invoice_amount:
+ type: integer
+ description: The amount in sats the buyer needs to submit an invoice of
+ to receive the trade amount
+ swap_allowed:
+ type: boolean
+ description: Whether on-chain swap is allowed
+ swap_failure_reason:
+ type: string
+ description: Reason for why on-chain swap is not available
+ suggested_mining_fee_rate:
+ type: integer
+ description: fee in sats/vbyte for the on-chain swap
+ swap_fee_rate:
+ type: number
+ format: double
+ description: in percentage, the swap fee rate the platform charges
+ pending_cancel:
+ type: boolean
+ description: Your counterparty requested for a collaborative cancel when
+ `status` is either `8`, `9` or `10`
+ asked_for_cancel:
+ type: boolean
+ description: You requested for a collaborative cancel `status` is either
+ `8`, `9` or `10`
+ statement_submitted:
+ type: boolean
+ description: True if you have submitted a statement. Available when `status`
+ is `11`
+ retries:
+ type: integer
+ description: Number of times ln node has tried to make the payment to you
+ (only if you are the buyer)
+ next_retry_time:
+ type: string
+ format: date-time
+ description: The next time payment will be retried. Payment is retried every
+ 1 sec
+ failure_reason:
+ type: string
+ description: The reason the payout failed
+ invoice_expired:
+ type: boolean
+ description: True if the payout invoice expired. `invoice_amount` will be
+ re-set and sent which means the user has to submit a new invoice to be
+ payed
+ public_duration:
+ type: integer
+ maximum: 86400
+ minimum: 597.6
+ bond_size:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
+ trade_fee_percent:
+ type: integer
+ description: The fee for the trade (fees differ for maker and taker)
+ bond_size_sats:
+ type: integer
+ description: The size of the bond in sats
+ bond_size_percent:
+ type: integer
+ description: same as `bond_size`
+ maker_summary:
+ $ref: '#/components/schemas/Summary'
+ taker_summary:
+ $ref: '#/components/schemas/Summary'
+ platform_summary:
+ $ref: '#/components/schemas/PlatformSummary'
+ expiry_reason:
+ nullable: true
+ minimum: 0
+ maximum: 32767
+ oneOf:
+ - $ref: '#/components/schemas/ExpiryReasonEnum'
+ - $ref: '#/components/schemas/NullEnum'
+ expiry_message:
+ type: string
+ description: The reason the order expired (message associated with the `expiry_reason`)
+ num_satoshis:
+ type: integer
+ description: only if status = `14` (Successful Trade) and is_buyer = `true`
+ sent_satoshis:
+ type: integer
+ description: only if status = `14` (Successful Trade) and is_buyer = `true`
+ txid:
+ type: string
+ description: Transaction id of the on-chain swap payout. Only if status
+ = `14` (Successful Trade) and is_buyer = `true`
+ network:
+ type: string
+ description: The network eg. 'testnet', 'mainnet'. Only if status = `14`
+ (Successful Trade) and is_buyer = `true`
+ latitude:
+ type: string
+ description: Latitude of the order for F2F payments
+ longitude:
+ type: string
+ description: Longitude of the order for F2F payments
+ required:
+ - expires_at
+ - id
+ - type
+ OrderPublic:
+ type: object
+ properties:
+ id:
+ type: integer
+ readOnly: true
+ created_at:
+ type: string
+ format: date-time
+ expires_at:
+ type: string
+ format: date-time
+ type:
+ allOf:
+ - $ref: '#/components/schemas/TypeEnum'
+ minimum: 0
+ maximum: 32767
+ currency:
+ type: integer
+ nullable: true
+ amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ has_range:
+ type: boolean
+ min_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ max_amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ payment_method:
+ type: string
+ maxLength: 70
+ is_explicit:
+ type: boolean
+ premium:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,2})?$
+ nullable: true
+ satoshis:
+ type: integer
+ maximum: 5000000
+ minimum: 20000
+ nullable: true
+ maker:
+ type: integer
+ nullable: true
+ maker_nick:
+ type: string
+ maker_status:
+ type: string
+ description: Status of the nick - "Active" or "Inactive"
+ price:
+ type: number
+ format: double
+ description: Price in order's fiat currency
+ escrow_duration:
+ type: integer
+ maximum: 28800
+ minimum: 1800
+ satoshis_now:
+ type: integer
+ description: The amount of sats to be traded at the present moment (not
+ including the fees)
+ bond_size:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,2}(?:\.\d{0,2})?$
+ latitude:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,2}(?:\.\d{0,6})?$
+ nullable: true
+ longitude:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,6})?$
+ nullable: true
+ required:
+ - expires_at
+ - id
+ - type
+ PlatformSummary:
+ type: object
+ properties:
+ contract_timestamp:
+ type: string
+ format: date-time
+ description: Timestamp of when the contract was finalized (price and sats
+ fixed)
+ contract_total_time:
+ type: number
+ format: double
+ description: The time taken for the contract to complete (from taker taking
+ the order to completion of order) in seconds
+ routing_fee_sats:
+ type: integer
+ description: Sats payed by the exchange for routing fees. Mining fee in
+ case of on-chain swap payout
+ trade_revenue_sats:
+ type: integer
+ description: The sats the exchange earned from the trade
+ PostMessage:
+ type: object
+ properties:
+ PGP_message:
+ type: string
+ nullable: true
+ maxLength: 5000
+ order_id:
+ type: integer
+ minimum: 0
+ description: Order ID of chatroom
+ offset:
+ type: integer
+ minimum: 0
+ nullable: true
+ description: Offset for message index to get as response
+ required:
+ - order_id
+ RatingEnum:
+ enum:
+ - '1'
+ - '2'
+ - '3'
+ - '4'
+ - '5'
+ type: string
+ description: |-
+ * `1` - 1
+ * `2` - 2
+ * `3` - 3
+ * `4` - 4
+ * `5` - 5
+ StatusEnum:
+ enum:
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+ - 6
+ - 7
+ - 8
+ - 9
+ - 10
+ - 11
+ - 12
+ - 13
+ - 14
+ - 15
+ - 16
+ - 17
+ - 18
+ type: integer
+ description: |-
+ * `0` - Waiting for maker bond
+ * `1` - Public
+ * `2` - Paused
+ * `3` - Waiting for taker bond
+ * `4` - Cancelled
+ * `5` - Expired
+ * `6` - Waiting for trade collateral and buyer invoice
+ * `7` - Waiting only for seller trade collateral
+ * `8` - Waiting only for buyer invoice
+ * `9` - Sending fiat - In chatroom
+ * `10` - Fiat sent - In chatroom
+ * `11` - In dispute
+ * `12` - Collaboratively cancelled
+ * `13` - Sending satoshis to buyer
+ * `14` - Sucessful trade
+ * `15` - Failed lightning network routing
+ * `16` - Wait for dispute resolution
+ * `17` - Maker lost dispute
+ * `18` - Taker lost dispute
+ Stealth:
+ type: object
+ properties:
+ wantsStealth:
+ type: boolean
+ required:
+ - wantsStealth
+ Summary:
+ type: object
+ properties:
+ sent_fiat:
+ type: integer
+ description: same as `amount` (only for buyer)
+ received_sats:
+ type: integer
+ description: same as `trade_satoshis` (only for buyer)
+ is_swap:
+ type: boolean
+ description: True if the payout was on-chain (only for buyer)
+ received_onchain_sats:
+ type: integer
+ description: The on-chain sats received (only for buyer and if `is_swap`
+ is `true`)
+ mining_fee_sats:
+ type: integer
+ description: Mining fees paid in satoshis (only for buyer and if `is_swap`
+ is `true`)
+ swap_fee_sats:
+ type: integer
+ description: Exchange swap fee in sats (i.e excluding miner fees) (only
+ for buyer and if `is_swap` is `true`)
+ swap_fee_percent:
+ type: number
+ format: double
+ description: same as `swap_fee_rate` (only for buyer and if `is_swap` is
+ `true`
+ sent_sats:
+ type: integer
+ description: The total sats you sent (only for seller)
+ received_fiat:
+ type: integer
+ description: same as `amount` (only for seller)
+ trade_fee_sats:
+ type: integer
+ description: Exchange fees in sats (Does not include swap fee and miner
+ fee)
+ Tick:
+ type: object
+ properties:
+ timestamp:
+ type: string
+ format: date-time
+ currency:
+ allOf:
+ - $ref: '#/components/schemas/Nested'
+ readOnly: true
+ volume:
+ type: string
+ format: decimal
+ nullable: true
+ price:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,14}(?:\.\d{0,2})?$
+ nullable: true
+ premium:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,2})?$
+ nullable: true
+ fee:
+ type: string
+ format: decimal
+ required:
+ - currency
+ TypeEnum:
+ enum:
+ - 0
+ - 1
+ type: integer
+ description: |-
+ * `0` - BUY
+ * `1` - SELL
+ UpdateOrder:
+ type: object
+ properties:
+ invoice:
+ type: string
+ nullable: true
+ description: |+
+ Invoice used for payouts. Must be PGP signed with the robot's public key. The expected Armored PGP header is -----BEGIN PGP SIGNED MESSAGE-----
+ Hash: SHA512
+
+ maxLength: 15000
+ routing_budget_ppm:
+ type: integer
+ maximum: 100001
+ minimum: 0
+ nullable: true
+ default: 0
+ description: Max budget to allocate for routing in PPM
+ address:
+ type: string
+ nullable: true
+ description: |+
+ Onchain address used for payouts. Must be PGP signed with the robot's public key. The expected Armored PGP header is -----BEGIN PGP SIGNED MESSAGE-----
+ Hash: SHA512
+
+ maxLength: 15000
+ statement:
+ type: string
+ nullable: true
+ maxLength: 500000
+ action:
+ $ref: '#/components/schemas/ActionEnum'
+ rating:
+ nullable: true
+ oneOf:
+ - $ref: '#/components/schemas/RatingEnum'
+ - $ref: '#/components/schemas/BlankEnum'
+ - $ref: '#/components/schemas/NullEnum'
+ amount:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,10}(?:\.\d{0,8})?$
+ nullable: true
+ mining_fee_rate:
+ type: string
+ format: decimal
+ pattern: ^-?\d{0,3}(?:\.\d{0,3})?$
+ nullable: true
+ required:
+ - action
+ Version:
+ type: object
+ properties:
+ major:
+ type: integer
+ minor:
+ type: integer
+ patch:
+ type: integer
+ required:
+ - major
+ - minor
+ - patch
+ securitySchemes:
+ tokenAuth:
+ type: apiKey
+ in: header
+ name: Authorization
+ description: Token-based authentication with required prefix "Token"
diff --git a/tests/test_api.py b/tests/test_api.py
new file mode 100644
index 00000000..99a5ec7f
--- /dev/null
+++ b/tests/test_api.py
@@ -0,0 +1,28 @@
+import urllib.request
+
+from openapi_tester.schema_tester import SchemaTester
+from rest_framework.response import Response
+from rest_framework.test import APITestCase
+
+# Update api specs to the newest from a running django server (if any)
+try:
+ urllib.request.urlretrieve(
+ "http://127.0.0.1:8000/api/schema", "tests/api_specs.yaml"
+ )
+except Exception as e:
+ print(f"Could not fetch current API specs: {e}")
+ print("Using previously existing api_specs.yaml definitions")
+
+schema_tester = SchemaTester(schema_file_path="tests/api_specs.yaml")
+
+
+class BaseAPITestCase(APITestCase):
+ @staticmethod
+ def assertResponse(response: Response, **kwargs) -> None:
+ """helper to run validate_response and pass kwargs to it"""
+
+ # List of endpoints with no available OpenAPI schema
+ skip_paths = ["/coordinator/login/"]
+
+ if response.request["PATH_INFO"] not in skip_paths:
+ schema_tester.validate_response(response=response, **kwargs)
diff --git a/tests/test_coordinator_info.py b/tests/test_coordinator_info.py
index 2abf984a..c4708f84 100644
--- a/tests/test_coordinator_info.py
+++ b/tests/test_coordinator_info.py
@@ -4,10 +4,12 @@ from unittest.mock import patch
from decouple import config
from django.conf import settings
from django.contrib.auth.models import User
-from django.test import Client, TestCase
+from django.test import Client
+from django.urls import reverse
from tests.mocks.cln import MockNodeStub
from tests.mocks.lnd import MockVersionerStub
+from tests.test_api import BaseAPITestCase
FEE = config("FEE", cast=float, default=0.2)
NODE_ID = config("NODE_ID", cast=str, default="033b58d7......")
@@ -18,7 +20,7 @@ NOTICE_SEVERITY = config("NOTICE_SEVERITY", cast=str, default="none")
NOTICE_MESSAGE = config("NOTICE_MESSAGE", cast=str, default="")
-class CoordinatorInfoTest(TestCase):
+class CoordinatorInfoTest(BaseAPITestCase):
su_pass = "12345678"
su_name = config("ESCROW_USERNAME", cast=str, default="admin")
@@ -32,12 +34,14 @@ class CoordinatorInfoTest(TestCase):
@patch("api.lightning.cln.node_pb2_grpc.NodeStub", MockNodeStub)
@patch("api.lightning.lnd.verrpc_pb2_grpc.VersionerStub", MockVersionerStub)
def test_info(self):
- path = "/api/info/"
+ path = reverse("info")
response = self.client.get(path)
data = json.loads(response.content.decode())
self.assertEqual(response.status_code, 200)
+ self.assertResponse(response)
+
self.assertEqual(data["num_public_buy_orders"], 0)
self.assertEqual(data["num_public_sell_orders"], 0)
self.assertEqual(data["book_liquidity"], 0)
diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py
index d6883bed..6109d9d1 100644
--- a/tests/test_trade_pipeline.py
+++ b/tests/test_trade_pipeline.py
@@ -5,7 +5,7 @@ from unittest.mock import patch
from decouple import config
from django.contrib.auth.models import User
-from django.test import Client, TestCase
+from django.urls import reverse
from api.management.commands.follow_invoices import Command as FollowInvoices
from api.models import Currency, Order
@@ -15,39 +15,84 @@ from tests.mocks.lnd import ( # MockRouterStub,; MockSignerStub,; MockVersioner
MockInvoicesStub,
MockLightningStub,
)
+from tests.test_api import BaseAPITestCase
-class TradeTest(TestCase):
+def read_file(file_path):
+ """
+ Read a file and return its content.
+ """
+ with open(file_path, "r") as file:
+ return file.read()
+
+
+class TradeTest(BaseAPITestCase):
su_pass = "12345678"
su_name = config("ESCROW_USERNAME", cast=str, default="admin")
- def setUp(self):
+ maker_form_with_range = {
+ "type": Order.Types.BUY,
+ "currency": 1,
+ "has_range": True,
+ "min_amount": 21,
+ "max_amount": 101.7,
+ "payment_method": "Advcash Cash F2F",
+ "is_explicit": False,
+ "premium": 3.34,
+ "public_duration": 69360,
+ "escrow_duration": 8700,
+ "bond_size": 3.5,
+ "latitude": 34.7455,
+ "longitude": 135.503,
+ }
+
+ @classmethod
+ def setUpTestData(cls):
"""
- Create a superuser. The superuser is the escrow party.
+ Set up initial data for the test case.
"""
- self.client = Client()
- User.objects.create_superuser(self.su_name, "super@user.com", self.su_pass)
+ # Create super user
+ User.objects.create_superuser(cls.su_name, "super@user.com", cls.su_pass)
+
+ # Fetch currency prices from external APIs
+ cache_market()
def test_login_superuser(self):
"""
- Test logging in as a superuser.
+ Test the login functionality for the superuser.
"""
- path = "/coordinator/login/"
+ path = reverse("admin:login")
data = {"username": self.su_name, "password": self.su_pass}
response = self.client.post(path, data)
self.assertEqual(response.status_code, 302)
+ self.assertResponse(response)
+
+ def test_cache_market(self):
+ """
+ Test if the cache_market() call during test setup worked
+ """
+ usd = Currency.objects.get(id=1)
+ self.assertIsInstance(
+ usd.exchange_rate,
+ Decimal,
+ f"Exchange rate is not a Decimal. Got {type(usd.exchange_rate)}",
+ )
+ self.assertGreater(
+ usd.exchange_rate, 0, "Exchange rate is not higher than zero"
+ )
+ self.assertIsInstance(
+ usd.timestamp, datetime, "External price timestamp is not a datetime"
+ )
def get_robot_auth(self, robot_index, first_encounter=False):
"""
Create an AUTH header that embeds token, pub_key, and enc_priv_key into a single string
as requested by the robosats token middleware.
"""
- with open(f"tests/robots/{robot_index}/b91_token", "r") as file:
- b91_token = file.read()
- with open(f"tests/robots/{robot_index}/pub_key", "r") as file:
- pub_key = file.read()
- with open(f"tests/robots/{robot_index}/enc_priv_key", "r") as file:
- enc_priv_key = file.read()
+
+ b91_token = read_file(f"tests/robots/{robot_index}/b91_token")
+ pub_key = read_file(f"tests/robots/{robot_index}/pub_key")
+ enc_priv_key = read_file(f"tests/robots/{robot_index}/enc_priv_key")
# First time a robot authenticated, it is registered by the backend, so pub_key and enc_priv_key is needed
if first_encounter:
@@ -59,14 +104,21 @@ class TradeTest(TestCase):
return headers, pub_key, enc_priv_key
- def assert_robot(self, response, pub_key, enc_priv_key, expected_nickname):
+ def assert_robot(self, response, pub_key, enc_priv_key, robot_index):
+ """
+ Assert that the robot is created correctly.
+ """
+ nickname = read_file(f"tests/robots/{robot_index}/nickname")
+
data = json.loads(response.content.decode())
self.assertEqual(response.status_code, 200)
+ self.assertResponse(response)
+
self.assertEqual(
data["nickname"],
- expected_nickname,
- "Robot created nickname is not MyopicRacket333",
+ nickname,
+ f"Robot created nickname is not {nickname}",
)
self.assertEqual(
data["public_key"], pub_key, "Returned public Kky does not match"
@@ -95,67 +147,37 @@ class TradeTest(TestCase):
"""
Creates the robots in /tests/robots/{robot_index}
"""
- path = "/api/robot/"
+ path = reverse("robot")
headers, pub_key, enc_priv_key = self.get_robot_auth(robot_index, True)
response = self.client.get(path, **headers)
- with open(f"tests/robots/{robot_index}/nickname", "r") as file:
- expected_nickname = file.read()
-
- self.assert_robot(response, pub_key, enc_priv_key, expected_nickname)
+ self.assert_robot(response, pub_key, enc_priv_key, robot_index)
def test_create_robots(self):
"""
- Creates two robots to be used in the trade tests
+ Test the creation of two robots to be used in the trade tests
"""
self.create_robot(robot_index=1)
self.create_robot(robot_index=2)
- def test_cache_market(self):
- cache_market()
-
- usd = Currency.objects.get(id=1)
- self.assertIsInstance(
- usd.exchange_rate,
- Decimal,
- f"Exchange rate is not a Decimal. Got {type(usd.exchange_rate)}",
- )
- self.assertGreater(
- usd.exchange_rate, 0, "Exchange rate is not higher than zero"
- )
- self.assertIsInstance(
- usd.timestamp, datetime, "External price timestamp is not a datetime"
- )
-
- def create_order(self, maker_form, robot_index=1):
- # Requisites
- # Cache market prices
- self.test_cache_market()
- path = "/api/make/"
+ def make_order(self, maker_form, robot_index=1):
+ """
+ Create an order for the test.
+ """
+ path = reverse("make")
# Get valid robot auth headers
headers, _, _ = self.get_robot_auth(robot_index, True)
response = self.client.post(path, maker_form, **headers)
return response
- def test_create_order(self):
- maker_form = {
- "type": Order.Types.BUY,
- "currency": 1,
- "has_range": True,
- "min_amount": 21,
- "max_amount": 101.7,
- "payment_method": "Advcash Cash F2F",
- "is_explicit": False,
- "premium": 3.34,
- "public_duration": 69360,
- "escrow_duration": 8700,
- "bond_size": 3.5,
- "latitude": 34.7455,
- "longitude": 135.503,
- }
- response = self.create_order(maker_form, robot_index=1)
+ def test_make_order(self):
+ """
+ Test the creation of an order.
+ """
+ maker_form = self.maker_form_with_range
+ response = self.make_order(maker_form, robot_index=1)
data = json.loads(response.content.decode())
# Checks
@@ -237,7 +259,7 @@ class TradeTest(TestCase):
@patch("api.lightning.lnd.lightning_pb2_grpc.LightningStub", MockLightningStub)
@patch("api.lightning.lnd.invoices_pb2_grpc.InvoicesStub", MockInvoicesStub)
def get_order(self, order_id, robot_index=1, first_encounter=False):
- path = "/api/order/"
+ path = reverse("order")
params = f"?order_id={order_id}"
headers, _, _ = self.get_robot_auth(robot_index, first_encounter)
response = self.client.get(path + params, **headers)
@@ -246,22 +268,10 @@ class TradeTest(TestCase):
def test_get_order_created(self):
# Make an order
- maker_form = {
- "type": Order.Types.BUY,
- "currency": 1,
- "has_range": True,
- "min_amount": 21,
- "max_amount": 101.7,
- "payment_method": "Advcash Cash F2F",
- "is_explicit": False,
- "premium": 3.34,
- "public_duration": 69360,
- "escrow_duration": 8700,
- "bond_size": 3.5,
- "latitude": 34.7455,
- "longitude": 135.503,
- }
- order_made_response = self.create_order(maker_form, robot_index=1)
+ maker_form = self.maker_form_with_range
+ robot_index = 1
+
+ order_made_response = self.make_order(maker_form, robot_index)
order_made_data = json.loads(order_made_response.content.decode())
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
@@ -284,7 +294,9 @@ class TradeTest(TestCase):
self.assertEqual(data["status_message"], Order.Status(Order.Status.WFB).label)
self.assertFalse(data["is_fiat_sent"])
self.assertFalse(data["is_disputed"])
- self.assertEqual(data["ur_nick"], "MyopicRacket333")
+ self.assertEqual(
+ data["ur_nick"], read_file(f"tests/robots/{robot_index}/nickname")
+ )
self.assertTrue(isinstance(data["satoshis_now"], int))
self.assertFalse(data["maker_locked"])
self.assertFalse(data["taker_locked"])
@@ -302,9 +314,9 @@ class TradeTest(TestCase):
follow_invoices = FollowInvoices()
follow_invoices.follow_hold_invoices()
- def create_and_publish_order(self, maker_form, robot_index=1):
+ def make_and_publish_order(self, maker_form, robot_index=1):
# Make an order
- order_made_response = self.create_order(maker_form, robot_index=1)
+ order_made_response = self.make_order(maker_form, robot_index)
order_made_data = json.loads(order_made_response.content.decode())
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
@@ -318,23 +330,9 @@ class TradeTest(TestCase):
return response
def test_publish_order(self):
- maker_form = {
- "type": Order.Types.BUY,
- "currency": 1,
- "has_range": True,
- "min_amount": 21,
- "max_amount": 101.7,
- "payment_method": "Advcash Cash F2F",
- "is_explicit": False,
- "premium": 3.34,
- "public_duration": 69360,
- "escrow_duration": 8700,
- "bond_size": 3.5,
- "latitude": 34.7455,
- "longitude": 135.503,
- }
+ maker_form = self.maker_form_with_range
# Get order
- response = self.create_and_publish_order(maker_form)
+ response = self.make_and_publish_order(maker_form)
data = json.loads(response.content.decode())
self.assertEqual(response.status_code, 200)
@@ -353,3 +351,66 @@ class TradeTest(TestCase):
self.assertFalse(public_data["is_participant"])
self.assertTrue(isinstance(public_data["price_now"], float))
self.assertTrue(isinstance(data["satoshis_now"], int))
+
+ @patch("api.lightning.cln.hold_pb2_grpc.HoldStub", MockHoldStub)
+ @patch("api.lightning.lnd.lightning_pb2_grpc.LightningStub", MockLightningStub)
+ @patch("api.lightning.lnd.invoices_pb2_grpc.InvoicesStub", MockInvoicesStub)
+ def take_order(self, order_id, amount, robot_index=2):
+ path = reverse("order")
+ params = f"?order_id={order_id}"
+ headers, _, _ = self.get_robot_auth(robot_index, first_encounter=True)
+ body = {"action": "take", "amount": amount}
+ response = self.client.post(path + params, body, **headers)
+
+ return response
+
+ def make_and_take_order(
+ self, maker_form, take_amount=80, maker_index=1, taker_index=2
+ ):
+ response_published = self.make_and_publish_order(maker_form, maker_index)
+ data_publised = json.loads(response_published.content.decode())
+ response = self.take_order(data_publised["id"], take_amount, taker_index)
+ return response
+
+ # def test_make_and_take_order(self):
+ # maker_index = 1
+ # taker_index = 2
+ # maker_form = self.maker_form_with_range
+ # self.create_robot(taker_index) #### WEEEE SHOULD NOT BE NEEDED >??? WHY ROBOT HAS NO LOGIN TIME??
+ # response = self.make_and_take_order(maker_form, 80, maker_index, taker_index)
+ # data = json.loads(response.content.decode())
+
+ # print(data)
+
+ # self.assertEqual(
+ # data["ur_nick"], read_file(f"tests/robots/{taker_index}/nickname")
+ # )
+ # self.assertEqual(
+ # data["taker_nick"], read_file(f"tests/robots/{taker_index}/nickname")
+ # )
+ # self.assertEqual(
+ # data["maker_nick"], read_file(f"tests/robots/{maker_index}/nickname")
+ # )
+ # self.assertFalse(data["is_maker"])
+ # self.assertTrue(data["is_taker"])
+ # self.assertTrue(data["is_participant"])
+
+ # a = {
+ # "maker_status": "Active",
+ # "taker_status": "Active",
+ # "price_now": 38205.0,
+ # "premium_now": 3.34,
+ # "satoshis_now": 266196,
+ # "is_buyer": False,
+ # "is_seller": True,
+ # "taker_nick": "EquivalentWool707",
+ # "status_message": "Waiting for taker bond",
+ # "is_fiat_sent": False,
+ # "is_disputed": False,
+ # "ur_nick": "EquivalentWool707",
+ # "maker_locked": True,
+ # "taker_locked": False,
+ # "escrow_locked": False,
+ # "bond_invoice": "lntb73280n1pj5uypwpp5vklcx3s3c66ltz5v7kglppke5n3u6sa6h8m6whe278lza7rwfc7qd2j2pshjmt9de6zqun9vejhyetwvdjn5gp3vgcxgvfkv43z6e3cvyez6dpkxejj6cnxvsmj6c3exsuxxden89skzv3j9cs9g6rfwvs8qcted4jkuapq2ay5cnpqgefy2326g5syjn3qt984253q2aq5cnz92skzqcmgv43kkgr0dcs9ymmzdafkzarnyp5kvgr5dpjjqmr0vd4jqampwvs8xatrvdjhxumxw4kzugzfwss8w6tvdssxyefqw4hxcmmrddjkggpgveskjmpfyp6kumr9wdejq7t0w5sxx6r9v96zqmmjyp3kzmnrv4kzqatwd9kxzar9wfskcmre9ccqz2sxqzfvsp5hkz0dnvja244hc8jwmpeveaxtjd4ddzuqlpqc5zxa6tckr8py50s9qyyssqdcl6w2rhma7k3v904q4tuz68z82d6x47dgflk6m8jdtgt9dg3n9304axv8qvd66dq39sx7yu20sv5pyguv9dnjw3385y8utadxxsqtsqpf7p3w",
+ # "bond_satoshis": 7328,
+ # }