mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 02:21:35 +00:00
Add OpenAPI v3.0 Docs for API (#243)
* Add initial api docs using drf-spectacular This commit adds the inital and basic docs using drf-spectacular. It also adds some serializers to be used for automatically generating response objects by drf-spectacular * Update api docs with correct request and response objects - TODO: /order route * Fix typo in api docs * Separate OpenAPI schemas into it's own file * Update drf-spectacular and add API intro and logo * Update API docs for GET /order * Add api docs for POST /order route * Update serializers.py
This commit is contained in:
parent
4d6f8067bb
commit
9a6d3d33a4
874
api/oas_schemas.py
Normal file
874
api/oas_schemas.py
Normal file
@ -0,0 +1,874 @@
|
|||||||
|
import textwrap
|
||||||
|
|
||||||
|
from decouple import config
|
||||||
|
from drf_spectacular.utils import OpenApiExample, OpenApiParameter
|
||||||
|
|
||||||
|
from api.serializers import (
|
||||||
|
ListOrderSerializer,
|
||||||
|
OrderDetailSerializer,
|
||||||
|
StealthSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
EXP_MAKER_BOND_INVOICE = int(config("EXP_MAKER_BOND_INVOICE"))
|
||||||
|
RETRY_TIME = int(config("RETRY_TIME"))
|
||||||
|
PUBLIC_DURATION = 60 * 60 * int(config("DEFAULT_PUBLIC_ORDER_DURATION")) - 1
|
||||||
|
ESCROW_DURATION = 60 * int(config("INVOICE_AND_ESCROW_DURATION"))
|
||||||
|
BOND_SIZE = int(config("DEFAULT_BOND_SIZE"))
|
||||||
|
|
||||||
|
|
||||||
|
class MakerViewSchema:
|
||||||
|
post = {
|
||||||
|
"summary": "Create a maker order",
|
||||||
|
"description": textwrap.dedent(
|
||||||
|
f"""
|
||||||
|
Create a new order as a maker.
|
||||||
|
|
||||||
|
|
||||||
|
Default values for the following fields if not specified:
|
||||||
|
- `public_duration` - **{PUBLIC_DURATION}**
|
||||||
|
- `escrow_duration` - **{ESCROW_DURATION}**
|
||||||
|
- `bond_size` - **{BOND_SIZE}**
|
||||||
|
- `bondless_taker` - **false**
|
||||||
|
- `has_range` - **false**
|
||||||
|
- `premium` - **0**
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
"responses": {
|
||||||
|
201: ListOrderSerializer,
|
||||||
|
400: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
409: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class OrderViewSchema:
|
||||||
|
get = {
|
||||||
|
"summary": "Get order details",
|
||||||
|
"description": textwrap.dedent(
|
||||||
|
"""
|
||||||
|
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`
|
||||||
|
- `bondless_taker`
|
||||||
|
- `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
|
||||||
|
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
"parameters": [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="order_id",
|
||||||
|
location=OpenApiParameter.QUERY,
|
||||||
|
required=True,
|
||||||
|
type=int,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
200: OrderDetailSerializer,
|
||||||
|
400: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
"default": "This order is not available",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
"default": "Invalid order Id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
OpenApiExample(
|
||||||
|
"Order cancelled",
|
||||||
|
value={"bad_request": "This order has been cancelled by the maker"},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"When the order is not public and you neither the taker nor maker",
|
||||||
|
value={"bad_request": "This order is not available"},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"Order cancelled",
|
||||||
|
value={"bad_request": "This order has been cancelled collaborativelly"},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"When maker bond expires (as maker)",
|
||||||
|
value={
|
||||||
|
"bad_request": "Invoice expired. You did not confirm publishing the order in time. Make a new order."
|
||||||
|
},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"When Robosats node is down",
|
||||||
|
value={
|
||||||
|
"bad_request": "The Lightning Network Daemon (LND) is down. Write in the Telegram group to make sure the staff is aware."
|
||||||
|
},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
take_update_confirm_dispute_cancel = {
|
||||||
|
"summary": "Update order",
|
||||||
|
"description": textwrap.dedent(
|
||||||
|
"""
|
||||||
|
Update an order
|
||||||
|
|
||||||
|
`action` field is required and determines what is to be done. Below
|
||||||
|
is an explaination 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. Make sure to perform this action only when
|
||||||
|
both the bonds are locked. i.e The status of your order is
|
||||||
|
atleast `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 recieved on-chain. 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` - Sucessful 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 returend your bond. This may change in
|
||||||
|
the future to prevent DDoSing the LN node and you won't be
|
||||||
|
returend 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 thier 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
|
||||||
|
recieving 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 recieved 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 perorming this action. Check that your fiat payment
|
||||||
|
method was successful in recieving 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).
|
||||||
|
- `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). `satement` must be sent in the
|
||||||
|
request body and should be a string. 100 chars < length of
|
||||||
|
`statement` < 5000 chars. You need to discribe the reason for
|
||||||
|
raising a dispute. The `(m|t)aker_statement` field is set
|
||||||
|
respectively. Only when both parties have submitted thier
|
||||||
|
dispute statement, the order status changes to `16` (Waiting
|
||||||
|
for dispute resolution)
|
||||||
|
- `rate_user`
|
||||||
|
- You can rate your counterparty using this action. You can rate
|
||||||
|
your user from `1-5` using the `rate` field in the request
|
||||||
|
body. Only allowed in the following states:
|
||||||
|
- `13` - Sending satoshis to buyer
|
||||||
|
- `14` - Sucessful trade
|
||||||
|
- `15` - Failed lightning network routing
|
||||||
|
- `17` - Maker lost dispute
|
||||||
|
- `18` - Taker lost dispute
|
||||||
|
- `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
|
||||||
|
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
"parameters": [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="order_id",
|
||||||
|
location=OpenApiParameter.QUERY,
|
||||||
|
required=True,
|
||||||
|
type=int,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
200: {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{"type": "str"},
|
||||||
|
{"type": "number"},
|
||||||
|
{"type": "object"},
|
||||||
|
{"type": "boolean"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
OpenApiExample(
|
||||||
|
"User not authenticated",
|
||||||
|
value={
|
||||||
|
"bad_request": "Woops! It seems you do not have a robot avatar",
|
||||||
|
},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewSchema:
|
||||||
|
post = {
|
||||||
|
"summary": "Create user",
|
||||||
|
"description": textwrap.dedent(
|
||||||
|
"""
|
||||||
|
Create a new Robot 🤖
|
||||||
|
|
||||||
|
`token_sha256` is the SHA256 hash of your token. 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 recieves the hash of the
|
||||||
|
token, it trusts the client with computing `length`, `counts` and `unique_values`
|
||||||
|
correctly. Check [here](https://github.com/Reckless-Satoshi/robosats/blob/main/frontend/src/utils/token.js#L13)
|
||||||
|
to see how the Javascript client copmutes these values. These values are optional,
|
||||||
|
but if provided, the api computes the entropy of the token adds two additional
|
||||||
|
fields to the response JSON - `token_shannon_entropy` and `token_bits_entropy`.
|
||||||
|
|
||||||
|
**Note: It is entirely the clients responsibilty to generate high entropy tokens, and the optional
|
||||||
|
parameters are provided to act as an aid to help determine sufficient entropy, but the server is happy
|
||||||
|
with just any sha256 hash you provide it**
|
||||||
|
|
||||||
|
`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 <key-id | email | name>
|
||||||
|
```
|
||||||
|
|
||||||
|
and it's private key can be exported in ascii armored format with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
gpg --export-secret-keys --armor <key-id | email | name>
|
||||||
|
```
|
||||||
|
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
"responses": {
|
||||||
|
201: {
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
"referral_code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "User's referral code",
|
||||||
|
},
|
||||||
|
"token_bits_entropy": {"type": "integer"},
|
||||||
|
"token_shannon_entropy": {"type": "integer"},
|
||||||
|
"wants_stealth": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False,
|
||||||
|
"description": "Whether the user prefers stealth invoices",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
202: {
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
"referral_code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "User's referral code",
|
||||||
|
},
|
||||||
|
"token_bits_entropy": {"type": "integer"},
|
||||||
|
"token_shannon_entropy": {"type": "integer"},
|
||||||
|
"wants_stealth": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False,
|
||||||
|
"description": "Whether the user prefers stealth invoices",
|
||||||
|
},
|
||||||
|
"found": {"type": "string", "description": "Welcome back message"},
|
||||||
|
"active_order_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Active order id if present",
|
||||||
|
},
|
||||||
|
"last_order_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Last order id if present",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"active_order_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Order id the robot is a maker/taker of",
|
||||||
|
},
|
||||||
|
"nickname": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username (Robot name)",
|
||||||
|
},
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
"default": "You are already logged in as {nickname} and have an active order",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "Response when you already authenticated and have an order",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
"default": "Enter a different token",
|
||||||
|
},
|
||||||
|
"found": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Bad luck, this nickname is taken",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
OpenApiExample(
|
||||||
|
"Successfuly created user",
|
||||||
|
value={
|
||||||
|
"token_shannon_entropy": 0.7714559798089662,
|
||||||
|
"token_bits_entropy": 169.21582985307933,
|
||||||
|
"nickname": "StackerMan420",
|
||||||
|
"referral_code": "lfvv4-ppNi1",
|
||||||
|
"public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n......\n......",
|
||||||
|
"encrypted_private_key": "-----BEGIN PGP PRIVATE KEY BLOCK-----\n\n......\n......",
|
||||||
|
"wants_stealth": False,
|
||||||
|
},
|
||||||
|
status_codes=[201],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"Already authenticated and have an order",
|
||||||
|
value={
|
||||||
|
"active_order_id": "42069",
|
||||||
|
"nickname": "StackerMan210",
|
||||||
|
"bad_request": "You are already logged in as {nickname} and have an active order",
|
||||||
|
},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"When required token entropy not met",
|
||||||
|
value={"bad_request": "The token does not have enough entropy"},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"Invalid PGP public key provided",
|
||||||
|
value={"bad_request": "Your PGP public key does not seem valid"},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = {
|
||||||
|
"summary": "Delete user",
|
||||||
|
"description": textwrap.dedent(
|
||||||
|
"""
|
||||||
|
Delete a Robot. Deleting a robot is not allowed if the robot has an active order, has had completed trades or was created more than 30 mins ago.
|
||||||
|
Mainly used on the frontend to "Generate new Robot" without flooding the DB with discarded robots.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
"responses": {
|
||||||
|
403: {},
|
||||||
|
400: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
301: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"user_deleted": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "User deleted permanently",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BookViewSchema:
|
||||||
|
get = {
|
||||||
|
"summary": "Get public orders",
|
||||||
|
"description": "Get public orders in the book.",
|
||||||
|
"parameters": [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="currency",
|
||||||
|
location=OpenApiParameter.QUERY,
|
||||||
|
description=(
|
||||||
|
"The currency id to filter by. Currency IDs can be found [here]"
|
||||||
|
"(https://github.com/Reckless-Satoshi/robosats/blob/main/frontend/static/assets/currencies.json). "
|
||||||
|
"Value of `0` means ANY currency"
|
||||||
|
),
|
||||||
|
type=int,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="type",
|
||||||
|
location=OpenApiParameter.QUERY,
|
||||||
|
description=(
|
||||||
|
"Order type to filter by\n"
|
||||||
|
"- `0` - BUY\n"
|
||||||
|
"- `1` - SELL\n"
|
||||||
|
"- `2` - ALL"
|
||||||
|
),
|
||||||
|
type=int,
|
||||||
|
enum=[0, 1, 2],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class InfoViewSchema:
|
||||||
|
get = {
|
||||||
|
"summary": "Get info",
|
||||||
|
"description": textwrap.dedent(
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
- Robot (If autheticated)
|
||||||
|
- nickname
|
||||||
|
- referral code
|
||||||
|
- earned rewards
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RewardViewSchema:
|
||||||
|
post = {
|
||||||
|
"summary": "Withdraw reward",
|
||||||
|
"description": "Withdraw user reward by submitting an invoice",
|
||||||
|
"responses": {
|
||||||
|
200: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"successful_withdrawal": {"type": "boolean", "default": True}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
"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": [
|
||||||
|
OpenApiExample(
|
||||||
|
"User not authenticated",
|
||||||
|
value={
|
||||||
|
"bad_request": "Woops! It seems you do not have a robot avatar",
|
||||||
|
},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"When no rewards earned",
|
||||||
|
value={
|
||||||
|
"successful_withdrawal": False,
|
||||||
|
"bad_invoice": "You have not earned rewards",
|
||||||
|
},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
OpenApiExample(
|
||||||
|
"Bad invoice or in case of payment failure",
|
||||||
|
value={
|
||||||
|
"successful_withdrawal": False,
|
||||||
|
"bad_invoice": "Does not look like a valid lightning invoice",
|
||||||
|
},
|
||||||
|
status_codes=[400],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PriceViewSchema:
|
||||||
|
get = {
|
||||||
|
"summary": "Get last market prices",
|
||||||
|
"description": "Get the last market price for each currency. Also, returns some more info about the last trade in each currency.",
|
||||||
|
"responses": {
|
||||||
|
200: {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"price": {"type": "integer"},
|
||||||
|
"volume": {"type": "integer"},
|
||||||
|
"premium": {"type": "integer"},
|
||||||
|
"timestamp": {"type": "string", "format": "date-time"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
OpenApiExample(
|
||||||
|
"Truncated example. Real response contains all the currencies",
|
||||||
|
value={
|
||||||
|
"<currency symbol>": {
|
||||||
|
"price": 21948.89,
|
||||||
|
"volume": 0.01366812,
|
||||||
|
"premium": 3.5,
|
||||||
|
"timestamp": "2022-09-13T14:32:40.591774Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status_codes=[200],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TickViewSchema:
|
||||||
|
get = {
|
||||||
|
"summary": "Get market ticks",
|
||||||
|
"description": "Get all market ticks. Returns a list of all the market ticks since inception.\n"
|
||||||
|
"CEX price is also recorded for useful insight on the historical premium of Non-KYC BTC. "
|
||||||
|
"Price is set when taker bond is locked.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LimitViewSchema:
|
||||||
|
get = {
|
||||||
|
"summary": "List order limits",
|
||||||
|
"description": "Get a list of order limits for every currency pair available.",
|
||||||
|
"responses": {
|
||||||
|
200: {
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
"max_bondless_amount": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Maximum amount allowed in a bondless order",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
OpenApiExample(
|
||||||
|
"Truncated example. Real response contains all the currencies",
|
||||||
|
value={
|
||||||
|
"<currency number>": {
|
||||||
|
"code": "USD",
|
||||||
|
"price": "42069.69",
|
||||||
|
"min_amount": "4.2",
|
||||||
|
"max_amount": "420.69",
|
||||||
|
"max_bondless_amount": "10.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status_codes=[200],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HistoricalViewSchema:
|
||||||
|
get = {
|
||||||
|
"summary": "Get historical exchange activity",
|
||||||
|
"description": "Get historical exchange activity. Currently, it lists each day's total contracts and their volume in BTC since inception.",
|
||||||
|
"responses": {
|
||||||
|
200: {
|
||||||
|
"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": [
|
||||||
|
OpenApiExample(
|
||||||
|
"Truncated example",
|
||||||
|
value={
|
||||||
|
"<date>": {
|
||||||
|
"code": "USD",
|
||||||
|
"price": "42069.69",
|
||||||
|
"min_amount": "4.2",
|
||||||
|
"max_amount": "420.69",
|
||||||
|
"max_bondless_amount": "10.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status_codes=[200],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StealthViewSchema:
|
||||||
|
put = {
|
||||||
|
"summary": "Update stealth option",
|
||||||
|
"description": "Update stealth invoice option for the user",
|
||||||
|
"responses": {
|
||||||
|
200: StealthSerializer,
|
||||||
|
400: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bad_request": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Reason for the failure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -1,5 +1,34 @@
|
|||||||
|
from django.template.defaultfilters import default
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import MarketTick, Order
|
from .models import MarketTick, Order
|
||||||
|
from decouple import config
|
||||||
|
|
||||||
|
RETRY_TIME = int(config("RETRY_TIME"))
|
||||||
|
MIN_PUBLIC_ORDER_DURATION_SECS=60*60*float(config("MIN_PUBLIC_ORDER_DURATION"))
|
||||||
|
MAX_PUBLIC_ORDER_DURATION_SECS=60*60*float(config("MAX_PUBLIC_ORDER_DURATION"))
|
||||||
|
|
||||||
|
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()
|
||||||
|
last_day_nonkyc_btc_premium = serializers.FloatField(help_text='Average premium (weighted by volume) of the orders in the last 24h')
|
||||||
|
last_day_volume = serializers.FloatField(help_text='Total volume in BTC in the last 24h')
|
||||||
|
lifetime_volume = serializers.FloatField(help_text='Total volume in BTC since exchange\'s inception')
|
||||||
|
lnd_version = serializers.CharField()
|
||||||
|
robosats_running_commit_hash = serializers.CharField()
|
||||||
|
alternative_site = serializers.CharField()
|
||||||
|
alternative_name = serializers.CharField()
|
||||||
|
node_alias = serializers.CharField()
|
||||||
|
node_id = serializers.CharField()
|
||||||
|
network = serializers.CharField()
|
||||||
|
maker_fee = serializers.FloatField(help_text='Exchange\'s set maker fee')
|
||||||
|
taker_fee = serializers.FloatField(help_text='Exchange\'s set taker fee ')
|
||||||
|
bond_size = serializers.FloatField(help_text='Default bond size (percent)')
|
||||||
|
current_swap_fee_rate = serializers.FloatField(help_text='Swap fees to perform on-chain transaction (percent)')
|
||||||
|
nickname = serializers.CharField(help_text='Currenlty logged in Robot name')
|
||||||
|
referral_code = serializers.CharField(help_text='Logged in users\'s referral code')
|
||||||
|
earned_rewards = serializers.IntegerField(help_text='Logged in user\'s earned rewards in satoshis')
|
||||||
|
|
||||||
|
|
||||||
class ListOrderSerializer(serializers.ModelSerializer):
|
class ListOrderSerializer(serializers.ModelSerializer):
|
||||||
@ -28,7 +57,421 @@ class ListOrderSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Only used in oas_schemas
|
||||||
|
class SummarySerializer(serializers.Serializer):
|
||||||
|
sent_fiat = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="same as `amount` (only for buyer)"
|
||||||
|
)
|
||||||
|
received_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="same as `trade_satoshis` (only for buyer)"
|
||||||
|
)
|
||||||
|
is_swap = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="True if the payout was on-chain (only for buyer)"
|
||||||
|
)
|
||||||
|
received_onchain_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The on-chain sats received (only for buyer and if `is_swap` is `true`)"
|
||||||
|
)
|
||||||
|
mining_fee_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="Mining fees paid in satoshis (only for buyer and if `is_swap` is `true`)"
|
||||||
|
)
|
||||||
|
swap_fee_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="Exchange swap fee in sats (i.e excluding miner fees) (only for buyer and if `is_swap` is `true`)"
|
||||||
|
)
|
||||||
|
swap_fee_percent = serializers.FloatField(
|
||||||
|
required=False,
|
||||||
|
help_text="same as `swap_fee_rate` (only for buyer and if `is_swap` is `true`"
|
||||||
|
)
|
||||||
|
sent_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The total sats you sent (only for seller)"
|
||||||
|
)
|
||||||
|
received_fiat = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="same as `amount` (only for seller)"
|
||||||
|
)
|
||||||
|
trade_fee_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="Exchange fees in sats (Does not include swap fee and miner fee)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Only used in oas_schemas
|
||||||
|
class PlatformSummarySerializer(serializers.Serializer):
|
||||||
|
contract_timestamp = serializers.DateTimeField(
|
||||||
|
required=False,
|
||||||
|
help_text="Timestamp of when the contract was finalized (price and sats fixed)"
|
||||||
|
)
|
||||||
|
contract_total_time = serializers.FloatField(
|
||||||
|
required=False,
|
||||||
|
help_text="The time taken for the contract to complete (from taker taking the order to completion of order) in seconds"
|
||||||
|
)
|
||||||
|
routing_fee_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="Sats payed by the exchange for routing fees. Mining fee in case of on-chain swap payout"
|
||||||
|
)
|
||||||
|
trade_revenue_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The sats the exchange earned from the trade"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Only used in oas_schemas
|
||||||
|
class OrderDetailSerializer(serializers.ModelSerializer):
|
||||||
|
total_secs_exp = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="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 = serializers.DateTimeField(
|
||||||
|
required=False,
|
||||||
|
help_text="Time when the user penalty will expire. Penalty applies when you create orders repeatedly without commiting a bond"
|
||||||
|
)
|
||||||
|
is_maker = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Whether you are the maker or not"
|
||||||
|
)
|
||||||
|
is_taker = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Whether you are the taker or not"
|
||||||
|
)
|
||||||
|
is_participant = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="True if you are either a taker or maker, False otherwise"
|
||||||
|
)
|
||||||
|
maker_status = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="Status of the maker:\n"
|
||||||
|
"- **'Active'** (seen within last 2 min)\n"
|
||||||
|
"- **'Seen Recently'** (seen within last 10 min)\n"
|
||||||
|
"- **'Inactive'** (seen more than 10 min ago)\n\n"
|
||||||
|
"Note: When you make a request to this route, your own status get's updated and can be seen by your counterparty"
|
||||||
|
)
|
||||||
|
taker_status = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="True if you are either a taker or maker, False otherwise"
|
||||||
|
)
|
||||||
|
price_now = serializers.IntegerField(
|
||||||
|
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(
|
||||||
|
required=False,
|
||||||
|
help_text="Premium over the CEX price at the current time"
|
||||||
|
)
|
||||||
|
premium_percentile = serializers.IntegerField(
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
num_similar_orders = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="(Only if `is_maker`) The number of public orders of the same currency currently in the order book"
|
||||||
|
)
|
||||||
|
tg_enabled = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="(Only if `is_maker`) Whether Telegram notification is enabled or not"
|
||||||
|
)
|
||||||
|
tg_token = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="(Only if `is_maker`) Your telegram bot token required to enable notifications."
|
||||||
|
)
|
||||||
|
tg_bot_name = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="(Only if `is_maker`) The Telegram username of the bot"
|
||||||
|
)
|
||||||
|
is_buyer = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Whether you are a buyer of sats (you will be receiving sats)"
|
||||||
|
)
|
||||||
|
is_seller = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Whether you are a seller of sats or not (you will be sending sats)"
|
||||||
|
)
|
||||||
|
maker_nick = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="Nickname (Robot name) of the maker"
|
||||||
|
)
|
||||||
|
taker_nick = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="Nickname (Robot name) of the taker"
|
||||||
|
)
|
||||||
|
status_message = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="The current status of the order corresponding to the `status`"
|
||||||
|
)
|
||||||
|
is_fiat_sent = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Whether or not the fiat amount is sent by the buyer"
|
||||||
|
)
|
||||||
|
is_disputed = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Whether or not the counterparty raised a dispute"
|
||||||
|
)
|
||||||
|
ur_nick = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="Your Nickname"
|
||||||
|
)
|
||||||
|
ur_nick = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="Your Nick"
|
||||||
|
)
|
||||||
|
maker_locked = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="True if maker bond is locked, False otherwise"
|
||||||
|
)
|
||||||
|
taker_locked = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="True if taker bond is locked, False otherwise"
|
||||||
|
)
|
||||||
|
escrow_locked = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="True if escrow is locked, False otherwise. Escrow is the sats to be sold, held by Robosats until the trade is finised."
|
||||||
|
)
|
||||||
|
trade_satoshis = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="Seller sees the amount of sats they need to send. Buyer sees the amount of sats they will receive "
|
||||||
|
)
|
||||||
|
bond_invoice = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="When `status` = `0`, `3`. Bond invoice to be paid"
|
||||||
|
)
|
||||||
|
bond_satoshis = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The bond amount in satoshis"
|
||||||
|
)
|
||||||
|
escrow_invoice = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="For the seller, the escrow invoice to be held by RoboSats"
|
||||||
|
)
|
||||||
|
escrow_satoshis = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The escrow amount in satoshis"
|
||||||
|
)
|
||||||
|
invoice_amount = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The amount in sats the buyer needs to submit an invoice of to receive the trade amount"
|
||||||
|
)
|
||||||
|
swap_allowed = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Whether on-chain swap is allowed"
|
||||||
|
)
|
||||||
|
swap_failure_reason = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="Reason for why on-chain swap is not available"
|
||||||
|
)
|
||||||
|
suggested_mining_fee_rate = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="fee in sats/vbyte for the on-chain swap"
|
||||||
|
)
|
||||||
|
swap_fee_rate = serializers.FloatField(
|
||||||
|
required=False,
|
||||||
|
help_text="in percentage, the swap fee rate the platform charges"
|
||||||
|
)
|
||||||
|
pending_cancel = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="Your counterparty requested for a collaborative cancel when `status` is either `8`, `9` or `10`"
|
||||||
|
)
|
||||||
|
asked_for_cancel = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="You requested for a collaborative cancel `status` is either `8`, `9` or `10`"
|
||||||
|
)
|
||||||
|
statement_submitted = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="True if you have submitted a statement. Available when `status` is `11`"
|
||||||
|
)
|
||||||
|
retries = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="Number of times ln node has tried to make the payment to you (only if you are the buyer)"
|
||||||
|
)
|
||||||
|
next_retry_time = serializers.DateTimeField(
|
||||||
|
required=False,
|
||||||
|
help_text=f"The next time payment will be retried. Payment is retried every {RETRY_TIME} sec"
|
||||||
|
)
|
||||||
|
failure_reason = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="The reason the payout failed"
|
||||||
|
)
|
||||||
|
invoice_expired = serializers.BooleanField(
|
||||||
|
required=False,
|
||||||
|
help_text="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"
|
||||||
|
)
|
||||||
|
trade_fee_percent = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The fee for the trade (fees differ for maker and taker)"
|
||||||
|
)
|
||||||
|
bond_size_sats = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="The size of the bond in sats"
|
||||||
|
)
|
||||||
|
bond_size_percent = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="same as `bond_size`"
|
||||||
|
)
|
||||||
|
maker_summary = SummarySerializer(required=False)
|
||||||
|
taker_summary = SummarySerializer(required=False)
|
||||||
|
platform_summary = PlatformSummarySerializer(required=True)
|
||||||
|
expiry_message = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="The reason the order expired (message associated with the `expiry_reason`)"
|
||||||
|
)
|
||||||
|
num_satoshis = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="only if status = `14` (Successful Trade) and is_buyer = `true`"
|
||||||
|
)
|
||||||
|
sent_satoshis = serializers.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="only if status = `14` (Successful Trade) and is_buyer = `true`"
|
||||||
|
)
|
||||||
|
txid = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="Transaction id of the on-chain swap payout. Only if status = `14` (Successful Trade) and is_buyer = `true`"
|
||||||
|
)
|
||||||
|
network = serializers.CharField(
|
||||||
|
required=False,
|
||||||
|
help_text="The network eg. 'testnet', 'mainnet'. Only if status = `14` (Successful Trade) and is_buyer = `true`"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Order
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"status",
|
||||||
|
"created_at",
|
||||||
|
"expires_at",
|
||||||
|
"type",
|
||||||
|
"currency",
|
||||||
|
"amount",
|
||||||
|
"has_range",
|
||||||
|
"min_amount",
|
||||||
|
"max_amount",
|
||||||
|
"payment_method",
|
||||||
|
"is_explicit",
|
||||||
|
"premium",
|
||||||
|
"satoshis",
|
||||||
|
"bondless_taker",
|
||||||
|
"maker",
|
||||||
|
"taker",
|
||||||
|
"escrow_duration",
|
||||||
|
"total_secs_exp",
|
||||||
|
"penalty",
|
||||||
|
"is_maker",
|
||||||
|
"is_taker",
|
||||||
|
"is_participant",
|
||||||
|
"maker_status",
|
||||||
|
"taker_status",
|
||||||
|
"price_now",
|
||||||
|
"premium",
|
||||||
|
"premium_percentile",
|
||||||
|
"num_similar_orders",
|
||||||
|
"tg_enabled",
|
||||||
|
"tg_token",
|
||||||
|
"tg_bot_name",
|
||||||
|
"is_buyer",
|
||||||
|
"is_seller",
|
||||||
|
"maker_nick",
|
||||||
|
"taker_nick",
|
||||||
|
"status_message",
|
||||||
|
"is_fiat_sent",
|
||||||
|
"is_disputed",
|
||||||
|
"ur_nick",
|
||||||
|
"ur_nick",
|
||||||
|
"maker_locked",
|
||||||
|
"taker_locked",
|
||||||
|
"escrow_locked",
|
||||||
|
"trade_satoshis",
|
||||||
|
"bond_invoice",
|
||||||
|
"bond_satoshis",
|
||||||
|
"escrow_invoice",
|
||||||
|
"escrow_satoshis",
|
||||||
|
"invoice_amount",
|
||||||
|
"swap_allowed",
|
||||||
|
'swap_failure_reason',
|
||||||
|
"suggested_mining_fee_rate",
|
||||||
|
"swap_fee_rate",
|
||||||
|
"pending_cancel",
|
||||||
|
"asked_for_cancel",
|
||||||
|
"statement_submitted",
|
||||||
|
"retries",
|
||||||
|
"next_retry_time",
|
||||||
|
"failure_reason",
|
||||||
|
"invoice_expired",
|
||||||
|
"public_duration",
|
||||||
|
"bond_size",
|
||||||
|
"trade_fee_percent",
|
||||||
|
"bond_size_sats",
|
||||||
|
"bond_size_percent",
|
||||||
|
"maker_summary",
|
||||||
|
"taker_summary",
|
||||||
|
"platform_summary",
|
||||||
|
"expiry_reason",
|
||||||
|
"expiry_message",
|
||||||
|
"num_satoshis",
|
||||||
|
"sent_satoshis",
|
||||||
|
"txid",
|
||||||
|
"network",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPublicSerializer(serializers.ModelSerializer):
|
||||||
|
maker_nick = serializers.CharField(required=False)
|
||||||
|
maker_status = serializers.CharField(help_text='Status of the nick - "Active" or "Inactive"', required=False)
|
||||||
|
price = serializers.IntegerField(help_text="Price in order's fiat currency", required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Order
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"expires_at",
|
||||||
|
"type",
|
||||||
|
"currency",
|
||||||
|
"amount",
|
||||||
|
"has_range",
|
||||||
|
"min_amount",
|
||||||
|
"max_amount",
|
||||||
|
"payment_method",
|
||||||
|
"is_explicit",
|
||||||
|
"premium",
|
||||||
|
"satoshis",
|
||||||
|
"bondless_taker",
|
||||||
|
"maker",
|
||||||
|
"maker_nick",
|
||||||
|
"maker_status",
|
||||||
|
"price",
|
||||||
|
"escrow_duration",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MakeOrderSerializer(serializers.ModelSerializer):
|
class MakeOrderSerializer(serializers.ModelSerializer):
|
||||||
|
currency = serializers.IntegerField(
|
||||||
|
required=True,
|
||||||
|
help_text="Currency id. See [here](https://github.com/Reckless-Satoshi/robosats/blob/main/frontend/static/assets/currencies.json) for a list of all IDs",
|
||||||
|
)
|
||||||
|
payment_method = serializers.CharField(
|
||||||
|
max_length=70,
|
||||||
|
default="not specified",
|
||||||
|
required=False,
|
||||||
|
help_text="Can be any string. The UI recognizes [these payment methods](https://github.com/Reckless-Satoshi/robosats/blob/main/frontend/src/components/payment-methods/Methods.js) and displays them with a logo."
|
||||||
|
)
|
||||||
|
is_explicit = serializers.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text='Whether the order is explicitly priced or not. If set to `true` then `satoshis` need to be specified'
|
||||||
|
)
|
||||||
|
has_range = serializers.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text='Whether the order specifies a range of amount or a fixed amount.\n\nIf `true`, then `min_amount` and `max_amount` fields are **required**.\n\n If `false` then `amount` is **required**',
|
||||||
|
)
|
||||||
|
bondless_taker = serializers.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text='Whether bondless takers are allowed for this order or not',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Order
|
model = Order
|
||||||
@ -111,25 +554,30 @@ class UserGenSerializer(serializers.Serializer):
|
|||||||
allow_null=True,
|
allow_null=True,
|
||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
required=False,
|
required=False,
|
||||||
default=None)
|
default=None,
|
||||||
|
help_text="Referal code")
|
||||||
counts = serializers.ListField(child=serializers.IntegerField(),
|
counts = serializers.ListField(child=serializers.IntegerField(),
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
required=False,
|
required=False,
|
||||||
default=None)
|
default=None,
|
||||||
|
help_text="Counts of the unique characters in the token")
|
||||||
length = serializers.IntegerField(allow_null=True,
|
length = serializers.IntegerField(allow_null=True,
|
||||||
default=None,
|
default=None,
|
||||||
required=False,
|
required=False,
|
||||||
min_value=1)
|
min_value=1,
|
||||||
|
help_text="Length of the token")
|
||||||
unique_values = serializers.IntegerField(allow_null=True,
|
unique_values = serializers.IntegerField(allow_null=True,
|
||||||
default=None,
|
default=None,
|
||||||
required=False,
|
required=False,
|
||||||
min_value=1)
|
min_value=1,
|
||||||
|
help_text="Number of unique values in the token")
|
||||||
|
|
||||||
class ClaimRewardSerializer(serializers.Serializer):
|
class ClaimRewardSerializer(serializers.Serializer):
|
||||||
invoice = serializers.CharField(max_length=2000,
|
invoice = serializers.CharField(max_length=2000,
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
default=None)
|
default=None,
|
||||||
|
help_text="A valid LN invoice with the reward amount to withdraw")
|
||||||
|
|
||||||
class PriceSerializer(serializers.Serializer):
|
class PriceSerializer(serializers.Serializer):
|
||||||
pass
|
pass
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import MakerView, OrderView, UserView, BookView, InfoView, RewardView, PriceView, LimitView, HistoricalView, TickView, StealthView
|
from .views import MakerView, OrderView, UserView, BookView, InfoView, RewardView, PriceView, LimitView, HistoricalView, TickView, StealthView
|
||||||
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||||
|
|
||||||
urlpatterns = [
|
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()),
|
||||||
path("order/",OrderView.as_view({
|
path("order/",OrderView.as_view({
|
||||||
"get": "get",
|
"get": "get",
|
||||||
|
36
api/views.py
36
api/views.py
@ -1,17 +1,22 @@
|
|||||||
import os
|
import os
|
||||||
from re import T
|
from re import T
|
||||||
from django.db.models import Sum, Q
|
from django.db.models import Sum, Q
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema
|
||||||
from rest_framework import status, viewsets
|
from rest_framework import status, viewsets
|
||||||
|
from rest_framework.exceptions import bad_request
|
||||||
from rest_framework.generics import CreateAPIView, ListAPIView, UpdateAPIView
|
from rest_framework.generics import CreateAPIView, ListAPIView, UpdateAPIView
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from api.oas_schemas import BookViewSchema, HistoricalViewSchema, InfoViewSchema, LimitViewSchema, MakerViewSchema, OrderViewSchema, PriceViewSchema, RewardViewSchema, StealthViewSchema, TickViewSchema, UserViewSchema
|
||||||
|
|
||||||
from api.serializers import ListOrderSerializer, MakeOrderSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer, TickSerializer, StealthSerializer
|
from api.serializers import InfoSerializer, ListOrderSerializer, MakeOrderSerializer, OrderPublicSerializer, UpdateOrderSerializer, ClaimRewardSerializer, PriceSerializer, UserGenSerializer, TickSerializer, StealthSerializer
|
||||||
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Currency, Profile
|
from api.models import LNPayment, MarketTick, OnchainPayment, Order, Currency, Profile
|
||||||
from control.models import AccountingDay, BalanceLog
|
from control.models import AccountingDay, BalanceLog
|
||||||
from api.logics import Logics
|
from api.logics import Logics
|
||||||
@ -46,6 +51,7 @@ avatar_path.mkdir(parents=True, exist_ok=True)
|
|||||||
class MakerView(CreateAPIView):
|
class MakerView(CreateAPIView):
|
||||||
serializer_class = MakeOrderSerializer
|
serializer_class = MakeOrderSerializer
|
||||||
|
|
||||||
|
@extend_schema(**MakerViewSchema.post)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
@ -97,6 +103,8 @@ class MakerView(CreateAPIView):
|
|||||||
if bondless_taker == None: bondless_taker = False
|
if bondless_taker == None: bondless_taker = False
|
||||||
if has_range == None: has_range = False
|
if has_range == None: has_range = False
|
||||||
|
|
||||||
|
# TODO add a check - if `is_explicit` is true then `satoshis` need to be specified
|
||||||
|
|
||||||
# An order can either have an amount or a range (min_amount and max_amount)
|
# An order can either have an amount or a range (min_amount and max_amount)
|
||||||
if has_range:
|
if has_range:
|
||||||
amount = None
|
amount = None
|
||||||
@ -122,6 +130,7 @@ class MakerView(CreateAPIView):
|
|||||||
status.HTTP_400_BAD_REQUEST,
|
status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Creates a new order
|
# Creates a new order
|
||||||
order = Order(
|
order = Order(
|
||||||
type=type,
|
type=type,
|
||||||
@ -158,6 +167,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
serializer_class = UpdateOrderSerializer
|
serializer_class = UpdateOrderSerializer
|
||||||
lookup_url_kwarg = "order_id"
|
lookup_url_kwarg = "order_id"
|
||||||
|
|
||||||
|
@extend_schema(**OrderViewSchema.get)
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
"""
|
"""
|
||||||
Full trade pipeline takes place while looking/refreshing the order page.
|
Full trade pipeline takes place while looking/refreshing the order page.
|
||||||
@ -419,6 +429,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
return Response(data, status.HTTP_200_OK)
|
return Response(data, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@extend_schema(**OrderViewSchema.take_update_confirm_dispute_cancel)
|
||||||
def take_update_confirm_dispute_cancel(self, request, format=None):
|
def take_update_confirm_dispute_cancel(self, request, format=None):
|
||||||
"""
|
"""
|
||||||
Here takes place all of the updates to the order object.
|
Here takes place all of the updates to the order object.
|
||||||
@ -548,6 +559,7 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
return self.get(request)
|
return self.get(request)
|
||||||
|
|
||||||
|
|
||||||
class UserView(APIView):
|
class UserView(APIView):
|
||||||
NickGen = NickGenerator(lang="English",
|
NickGen = NickGenerator(lang="English",
|
||||||
use_adv=False,
|
use_adv=False,
|
||||||
@ -557,6 +569,7 @@ class UserView(APIView):
|
|||||||
|
|
||||||
serializer_class = UserGenSerializer
|
serializer_class = UserGenSerializer
|
||||||
|
|
||||||
|
|
||||||
def post(self, request, format=None):
|
def post(self, request, format=None):
|
||||||
"""
|
"""
|
||||||
Get a new user derived from a high entropy token
|
Get a new user derived from a high entropy token
|
||||||
@ -711,6 +724,7 @@ class UserView(APIView):
|
|||||||
context["bad_request"] = "Enter a different token"
|
context["bad_request"] = "Enter a different token"
|
||||||
return Response(context, status.HTTP_403_FORBIDDEN)
|
return Response(context, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
@extend_schema(**UserViewSchema.delete)
|
||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
"""Pressing "give me another" deletes the logged in user"""
|
"""Pressing "give me another" deletes the logged in user"""
|
||||||
user = request.user
|
user = request.user
|
||||||
@ -750,9 +764,10 @@ class UserView(APIView):
|
|||||||
|
|
||||||
|
|
||||||
class BookView(ListAPIView):
|
class BookView(ListAPIView):
|
||||||
serializer_class = ListOrderSerializer
|
serializer_class = OrderPublicSerializer
|
||||||
queryset = Order.objects.filter(status=Order.Status.PUB)
|
queryset = Order.objects.filter(status=Order.Status.PUB)
|
||||||
|
|
||||||
|
@extend_schema(**BookViewSchema.get)
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
currency = request.GET.get("currency", 0)
|
currency = request.GET.get("currency", 0)
|
||||||
type = request.GET.get("type", 2)
|
type = request.GET.get("type", 2)
|
||||||
@ -794,8 +809,12 @@ class BookView(ListAPIView):
|
|||||||
|
|
||||||
return Response(book_data, status=status.HTTP_200_OK)
|
return Response(book_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class InfoView(ListAPIView):
|
class InfoView(ListAPIView):
|
||||||
|
|
||||||
|
serializer_class = InfoSerializer
|
||||||
|
|
||||||
|
@extend_schema(**InfoViewSchema.get)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
@ -872,6 +891,7 @@ class InfoView(ListAPIView):
|
|||||||
class RewardView(CreateAPIView):
|
class RewardView(CreateAPIView):
|
||||||
serializer_class = ClaimRewardSerializer
|
serializer_class = ClaimRewardSerializer
|
||||||
|
|
||||||
|
@extend_schema(**RewardViewSchema.post)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
@ -897,10 +917,12 @@ class RewardView(CreateAPIView):
|
|||||||
|
|
||||||
return Response({"successful_withdrawal": True}, status.HTTP_200_OK)
|
return Response({"successful_withdrawal": True}, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class PriceView(ListAPIView):
|
class PriceView(ListAPIView):
|
||||||
|
|
||||||
serializer_class = PriceSerializer
|
serializer_class = PriceSerializer
|
||||||
|
|
||||||
|
@extend_schema(**PriceViewSchema.get)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
payload = {}
|
payload = {}
|
||||||
@ -921,17 +943,21 @@ class PriceView(ListAPIView):
|
|||||||
|
|
||||||
return Response(payload, status.HTTP_200_OK)
|
return Response(payload, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class TickView(ListAPIView):
|
class TickView(ListAPIView):
|
||||||
|
|
||||||
queryset = MarketTick.objects.all()
|
queryset = MarketTick.objects.all()
|
||||||
serializer_class = TickSerializer
|
serializer_class = TickSerializer
|
||||||
|
|
||||||
|
@extend_schema(**TickViewSchema.get)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
data = self.serializer_class(self.queryset.all(), many=True, read_only=True).data
|
data = self.serializer_class(self.queryset.all(), many=True, read_only=True).data
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class LimitView(ListAPIView):
|
class LimitView(ListAPIView):
|
||||||
|
|
||||||
|
@extend_schema(**LimitViewSchema.get)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
# Trade limits as BTC
|
# Trade limits as BTC
|
||||||
@ -955,7 +981,10 @@ class LimitView(ListAPIView):
|
|||||||
|
|
||||||
return Response(payload, status.HTTP_200_OK)
|
return Response(payload, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class HistoricalView(ListAPIView):
|
class HistoricalView(ListAPIView):
|
||||||
|
|
||||||
|
@extend_schema(**HistoricalViewSchema.get)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
payload = {}
|
payload = {}
|
||||||
queryset = AccountingDay.objects.all().order_by('day')
|
queryset = AccountingDay.objects.all().order_by('day')
|
||||||
@ -968,10 +997,11 @@ class HistoricalView(ListAPIView):
|
|||||||
|
|
||||||
return Response(payload, status.HTTP_200_OK)
|
return Response(payload, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class StealthView(UpdateAPIView):
|
class StealthView(UpdateAPIView):
|
||||||
|
|
||||||
serializer_class = StealthSerializer
|
serializer_class = StealthSerializer
|
||||||
|
@extend_schema(**StealthViewSchema.put)
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
|
@ -27,3 +27,5 @@ django-import-export==2.7.1
|
|||||||
requests[socks]
|
requests[socks]
|
||||||
python-gnupg==0.4.9
|
python-gnupg==0.4.9
|
||||||
daphne==3.0.2
|
daphne==3.0.2
|
||||||
|
drf-spectacular==0.24.0
|
||||||
|
drf-spectacular-sidecar==2022.9.1
|
||||||
|
@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/4.0/ref/settings/
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import textwrap
|
||||||
from decouple import config
|
from decouple import config
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
@ -89,7 +90,47 @@ INSTALLED_APPS = [
|
|||||||
"chat",
|
"chat",
|
||||||
"control",
|
"control",
|
||||||
"frontend.apps.FrontendConfig",
|
"frontend.apps.FrontendConfig",
|
||||||
|
"drf_spectacular",
|
||||||
|
"drf_spectacular_sidecar", # required for Django collectstatic discovery
|
||||||
]
|
]
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||||
|
}
|
||||||
|
|
||||||
|
SPECTACULAR_SETTINGS = {
|
||||||
|
'TITLE': 'RoboSats REST API v0',
|
||||||
|
'DESCRIPTION': textwrap.dedent(
|
||||||
|
"""
|
||||||
|
REST API Documentation for [RoboSats](https://learn.robosats.com) - A Simple and Private LN P2P Exchange
|
||||||
|
|
||||||
|
<p style='background-color:#fff0f0;padding:16px;border-radius:6px;border:2px solid #ffd3d3'>
|
||||||
|
<span style='color:#f31f1f;font-weight:bold'>Note:</span>
|
||||||
|
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™.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
'VERSION': '0.1.0',
|
||||||
|
'SERVE_INCLUDE_SCHEMA': False,
|
||||||
|
'SWAGGER_UI_DIST': 'SIDECAR', # shorthand to use the sidecar instead
|
||||||
|
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
|
||||||
|
'REDOC_UI_SETTINGS': {
|
||||||
|
'expandResponses': '200,201',
|
||||||
|
},
|
||||||
|
'EXTENSIONS_INFO': {
|
||||||
|
'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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'REDOC_DIST': 'SIDECAR',
|
||||||
|
}
|
||||||
|
|
||||||
from .celery.conf import *
|
from .celery.conf import *
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
Loading…
Reference in New Issue
Block a user