Skip to content
This repository has been archived by the owner on Jun 15, 2021. It is now read-only.

WIP Produce the simplest-possible getting started guide for the Bisq HTTP API #54

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 256 additions & 0 deletions http-api-monitor-offers.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
= Monitoring Bisq offers with Http API

This guide walks you through the process of creating a simple bot that monitors available offers.


== What you'll build

You'll build a NodeJS-based script that connects to Bisq over an HTTP API to get offers and market prices and then displays "interesting" offers on the console if any are found.
Copy link
Contributor

@m52go m52go Jun 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's officially 'Node.js'. Just concerned the JS hipster 'ninjas' will get us if we don't get this right...


CAUTION: The Bisq HTTP API is currently incubating. Things may be rough around the edges and should not be considered production-ready. Your feedback is extremely valuable at this stage—see the link:#next-steps[next steps] section at bottom for details how to get in touch with us. Thanks!


== What you’ll need

* About 15 minutes
* A favorite text editor or IDE
* NodeJS 6+ (to execute the API client script that you will write)
* Either:
** Docker (to run from an image) or
** Git, Maven and JDK8 (to build from source)

NOTE: Using the Bisq HTTP API in no way requires the use of NodeJS on the client side. It is just used for example purposes in this guide.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest removing the parenthetical note above ("to execute the API client script...") and making this note more detailed.

Specifically, explicitly state:

  • Node.js is not a dependency of the HTTP API.
  • It's only used here to make the tasks of making HTTP requests and manipulating responses easier for the purposes of this example, but that those tasks can be done with any tools the user prefers.


== Run the API

There are two ways you can run an instance of the Bisq HTTP API: from a Docker image or from source.

=== Run the API using Docker

The easiest way to run the API in headless mode is by using a Docker image:
Copy link
Contributor

@m52go m52go Jun 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could 'headless' be clarified here? Knowing virtually nothing about how this API works, my initial impression was that the API would query my local Bisq client for data, and that 'headless' simply meant 'no GUI' but that's clearly not the case.

Basically I wasn't sure if the API needed my Bisq client to be running in the background.

A quick 1 or 2 lines about what these images actually do / how the API works might help?

Also, how does one know when the Docker image is done setting up? Is it when the Bisq ASCII art shows? I watched the terminal for a while until I realized that what I was seeing was actually streaming data, and API calls were already working.


docker run -it --rm -p 8080:8080 -e BISQ_API_HOST=0.0.0.0 bisq/api

=== Run the API from source

For more hard-core developers that want to run from source:

git clone https://github.com/mrosseel/bisq-api
cd bisq-api
mvn compile exec:java \
-Dexec.mainClass="network.bisq.api.app.ApiMain" \
-Dexec.args="--appName=http-api-monitor-offers"

[NOTE]
Since the API is incubating we suggest to run it against a different database directory from the default one.
To do that, use the `--appName` parameter. You can set it to whatever you like (just keep it different from `Bisq`).

=== Verify the API is running

The API should now be running on port 8080. You can verify that by executing:

curl http://localhost:8080/api/v1/version

You should receive a response like the following:

[source,json]
----
{
"application": "0.6.7",
"network": 1,
"p2PMessage": 10,
"localDB": 1,
"tradeProtocol": 1
}
----

[NOTE]
Complete interactive API documentation is available at http://localhost:8080/swagger.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this stand out a bit more. I don't know if it was just me, but I glazed over it the first few times I saw it. (I also wasn't aware of Swagger, so I just thought it was a cool pathname).

Maybe take it out of a NOTE and make it something like "*Complete API documentation is available at http://localhost:8080/swagger*. It's interactive, so you can play around with it right in your browser."


== API overview

First we will look at the endpoints we need to fetch the data from and how their responses look.

=== List available offers

Open offers are available at http://localhost:8080/api/v1/offers.

The response should look like this:

[source,json]
----
{
"offers": [OfferDetail],
"total": integer
}
----

The `offers` property is an array with individual offers and `total` is the number of all offers. The model for each `OfferDetail` element is defined as follows:

[source,json]
----
{
"acceptedBankIds": [string]
"acceptedCountryCodes": [string]
"amount": integer
"arbitratorNodeAddresses": [string]
"bankId": string
"baseCurrencyCode": string
"blockHeightAtOfferCreation": integer
"buyerSecurityDeposit": integer
"counterCurrencyCode": string
"countryCode": string
"currencyCode": string
"date": string
"direction": string Enum: [ BUY, SELL ]
"hashOfChallenge": string
"id": string
"isCurrencyForMakerFeeBtc": boolean
"isPrivateOffer": boolean
"lowerClosePrice": integer
"makerFee": integer
"makerPaymentAccountId": string
"marketPriceMargin": number
"maxTradeLimit": integer
"maxTradePeriod": integer
"minAmount": integer
"offerFeePaymentTxId": string
"ownerNodeAddress": string
"paymentMethodId": string
"price": integer
"protocolVersion": integer
"sellerSecurityDeposit": integer
"state": string Enum: [ UNKNOWN, OFFER_FEE_PAID, AVAILABLE, NOT_AVAILABLE, REMOVED, MAKER_OFFLINE ]
"txFee": integer
"upperClosePrice": integer
"useAutoClose": boolean
"useMarketBasedPrice": boolean
"useReOpenAfterAutoClose": boolean
"versionNr": string
}
----

Now let's assume that we want to buy bitcoin, and let's define "interesting" offers as those that:

. sell bitcoin at a 1% discount or more under the current market price, and
. accept payment in Euros to a Polish SEPA account

First we need to filter those offers using following static criteria:

[source,json]
----
{
"baseCurrencyCode": "BTC",
"counterCurrencyCode": "EUR",
"direction": "SELL",
"paymentMethodId": "SEPA"
}
----

Next we need to filter those offers by price. There are two types of offers: _market-based price offers_ and _fixed price offers_. They are distinguished by the `useMarketBasedPrice` attribute. In the case of market-based price offers the filtering criteria is easy: the `marketPriceMargin` value must be above 0.1. In the case of fixed price offers we have to fetch the market price of BTC in EUR, calculate whether the price is 1% or less, and then filter offers which have a price _less_ than that calculated price.
Copy link
Contributor

@m52go m52go Jun 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"On Bisq, there are two types of offers..." [Suggestion]

"In the case of fixed price offers we have to fetch the market price of BTC in EUR, calculate a threshold price that fits our needs (i.e., the price that's 1% lower than the market price), and then find all offers which have a price less than that threshold price." [Suggestion—I found this wording confusing]


=== Get the market price

In order to get the market price of BTC in EUR, execute the following query:

curl http://localhost:8080/api/v1/currencies/prices?currencyCodes=EUR

You should receive a response like the following:

[source,json]
----
{
"prices": {
"EUR": 7035.62
}
}
----


== Write the monitoring bot code

Let's install some dependencies:

npm install lodash http-as-promised
Copy link
Contributor

@m52go m52go Jun 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be helpful to mention adding the require() statements here too.

I built the script as I read along, and ended up with errors because I didn't require() those modules. It's a super-simple thing that most people will figure out quickly, and I know it's already in the full script at the bottom, but just a quick mention along the lines of "don't forget to require these modules" or something might be nice.


In general our script will look like this:

.http-api-monitor-offers.js
[source,javascript]
----
include::http-api-monitor-offers.js[tags=flow]
----

So first 2 things to do (concurrently and asynchronously) is to fetch offers and market price of BTC from our API. Then we need to filter those offers and display the results.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure "(concurrently and asynchronously)" adds any value.


Getting offers is a simple call that returns a promise with an array of offers:

.http-api-monitor-offers.js
[source,javascript]
----
include::http-api-monitor-offers.js[tags=getOffers]
----

Similarly with `getMarketPrice`:

.http-api-monitor-offers.js
[source,javascript]
----
include::http-api-monitor-offers.js[tags=getMarketPrice]
----

Now our `filterOffers` function is ready to receive an array of results from the two functions described above:

.http-api-monitor-offers.js
[source,javascript]
----
include::http-api-monitor-offers.js[tags=filterOffers]
----

This function filters offers to match our criteria. It returns matching offers and maps them to a simpler structure that contains as little data as needed for the `notify` function. We are using the `lodash` library to simplify filtering.

The `getPriceFilter` function creates the actual filter function and looks like this:

.http-api-monitor-offers.js
[source,javascript]
----
include::http-api-monitor-offers.js[tags=getPriceFilter]
----

We are multiplying `marketPrice` by `10000` because that is the format in which the API returns the price.

Here is a full script.

.http-api-monitor-offers.js
[source,javascript]
----
include::http-api-monitor-offers.js[tags=**;*]
----

Now run

node http-api-monitor-offers.js

If there are any matching offers you should see something like this:

5 interesting BTC offers from Bisq
0.0625 BTC (-2%)
0.01 BTC (-2%)
0.01 BTC (-5%)
0.033 BTC (-3%)
0.02 BTC (-1.5%)
0.25 BTC (-6%)

If there are no matching offers, try fiddling with the value of `threshold`. Try setting it to -20, 0.01, etc.


== Summary

Congratulations! You are now able to monitor Bisq offers via the Bisq HTTP API. From here, you can explore the complete API documentation and build trading bots of any complexity and in any language you like.


== Next steps

* Try adding a loop to keep the process repeating the search periodically (use _setInterval_)
* Join our slack at https://bisq.slack.com and leave feedback on the API and this guide
* If you find any issues please report them https://github.com/mrosseel/bisq-api/issues[here]
62 changes: 62 additions & 0 deletions http-api-monitor-offers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const _ = require('lodash');
const $http = require('http-as-promised');

const threshold = 0.1;

//tag::getPriceFilter[]
function getPriceFilter(marketPrice) {
const maxPrice = marketPrice * (1 - threshold) * 10000;
return offer => {
if (offer.useMarketBasedPrice)
return offer.marketPriceMargin >= threshold;
return offer.price < maxPrice;
}
}
//end::getPriceFilter[]

//tag::getMarketPrice[]
function getMarketPrice() {
return $http.get('http://localhost:8080/api/v1/currencies/prices?currencyCodes=EUR', {resolve: 'body', json: true})
.then(body => _.get(body, 'prices.EUR'))
}
//end::getMarketPrice[]

//tag::getOffers[]
function getOffers() {
return $http.get('http://localhost:8080/api/v1/offers', {resolve: 'body', json: true}).then(body => body.offers);
}
//end::getOffers[]

function notify(offers) {
Copy link
Contributor

@m52go m52go Jun 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from the require() statements, this notify() method was the one other item that wasn't mentioned in the text that I didn't have in my code when I ran the script for the first time.

Just FYI. I guess it's because this is structured as a bottom-up walk-through. If it were reversed (i.e., top-down approach with full script first and then important parts clarified), it would be fine to pick and choose what to cover.

if (!offers.length) {
console.log('No interesting offers found');
return;
}

const text = _.map(offers, offer => `${offer.amount / 100000000} BTC (-${_.round(offer.margin * 100, 2)}%)`).join('\n');
console.info(text);
}

//tag::filterOffers[]
function filterOffers([offers, marketPrice]) {
return _(offers)
.filter({
baseCurrencyCode: 'BTC',
counterCurrencyCode: 'EUR',
direction: 'SELL',
paymentMethodId: 'SEPA'
})
.filter(i => _.includes(i.acceptedCountryCodes, 'PL'))
.filter(getPriceFilter(marketPrice))
.map(i => _.pick(i, 'baseCurrencyCode', 'counterCurrencyCode', 'direction', 'paymentMethodId', 'id', 'useMarketBasedPrice', 'price', 'marketPriceMargin', 'amount', 'minAmont'))
.map(i => ({amount: i.amount, margin: i.useMarketBasedPrice ? i.marketPriceMargin : marketPrice / i.price}))
.value();
}
//end::filterOffers[]

//tag::flow[]
Promise.all([getOffers(), getMarketPrice()])
.then(filterOffers)
.then(notify)
.catch(e => console.error(e));
//end::flow[]
2 changes: 1 addition & 1 deletion index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Docs without hyperlinks haven't been written yet. If you want to write one, <<co

=== Incubating Efforts

* Bisq HTTP API — _Automate trading and interact programmatically with your Bisq node_
* <<http-api-monitor-offers#, Bisq HTTP API>> — _Automate trading and interact programmatically with your Bisq node_
* Bisq Mobile — _Operate your Bisq node remotely via a web/mobile frontend_

== Contributor Docs
Expand Down