mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-18 15:10:03 +00:00
exchanges: Remove LocalBitcoins and fix Bybit tests (#1142)
This commit is contained in:
@@ -40,7 +40,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| ItBit | Yes | NA | No |
|
||||
| Kraken | Yes | Yes | NA |
|
||||
| Lbank | Yes | No | NA |
|
||||
| LocalBitcoins | Yes | NA | NA |
|
||||
| OKCoin International | Yes | Yes | No |
|
||||
| Okx | Yes | Yes | NA |
|
||||
| Poloniex | Yes | Yes | NA |
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
gctfile "github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -48,7 +47,6 @@ const (
|
||||
pathKraken = "https://www.kraken.com/features/api"
|
||||
pathAlphaPoint = "https://alphapoint.github.io/slate/#introduction"
|
||||
pathYobit = "https://www.yobit.net/en/api/"
|
||||
pathLocalBitcoins = "https://localbitcoins.com/api-docs/"
|
||||
pathGetAllLists = "https://api.trello.com/1/boards/%s/lists?cards=none&card_fields=all&filter=open&fields=all&key=%s&token=%s"
|
||||
pathNewCard = "https://api.trello.com/1/cards?idList=%s&name=%s&key=%s&token=%s"
|
||||
pathChecklists = "https://api.trello.com/1/checklists/%s/checkItems?%s&key=%s&token=%s"
|
||||
@@ -488,8 +486,6 @@ func checkChangeLog(htmlData *HTMLScrapingData) (string, error) {
|
||||
dataStrings, err = htmlScrapeAlphaPoint(htmlData)
|
||||
case pathYobit:
|
||||
dataStrings, err = htmlScrapeYobit(htmlData)
|
||||
case pathLocalBitcoins:
|
||||
dataStrings, err = htmlScrapeLocalBitcoins(htmlData)
|
||||
case pathOkCoin:
|
||||
dataStrings, err = htmlScrapeOk(htmlData)
|
||||
default:
|
||||
@@ -1137,32 +1133,6 @@ loop:
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// htmlScrapeLocalBitcoins gets the check string for Yobit Exchange
|
||||
func htmlScrapeLocalBitcoins(htmlData *HTMLScrapingData) ([]string, error) {
|
||||
temp, err := sendHTTPGetRequest(htmlData.Path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer temp.Body.Close()
|
||||
|
||||
a, err := io.ReadAll(temp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := regexp.Compile(htmlData.RegExp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
str := r.FindString(string(a))
|
||||
sha, err := crypto.GetSHA256([]byte(str))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []string
|
||||
resp = append(resp, crypto.HexEncodeToString(sha))
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// trelloCreateNewCheck creates a new checklist item within a given checklist from trello
|
||||
func trelloCreateNewCheck(newCheckName string) error {
|
||||
newName, err := nameStateChanges(newCheckName, "")
|
||||
|
||||
@@ -386,16 +386,6 @@ func TestHTMLYobit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTMLScrapeLocalBitcoins(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := HTMLScrapingData{TokenData: "div",
|
||||
RegExp: `col-md-12([\s\S]*?)clearfix`,
|
||||
Path: "https://localbitcoins.com/api-docs/"}
|
||||
if _, err := htmlScrapeLocalBitcoins(&data); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTMLScrapeOk(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := HTMLScrapingData{TokenData: "a",
|
||||
|
||||
@@ -327,21 +327,6 @@
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "LocalBitcoins",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "div",
|
||||
"Key": "class",
|
||||
"Val": "col-md-12",
|
||||
"RegExp": "col-md-12([\\s\\S]*?)clearfix",
|
||||
"CheckString": "37a144dc619776b87c098da5a88bef7fed6c8a7cea2d4b9a38c96750726c93ff",
|
||||
"Path": "https://localbitcoins.com/api-docs/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "OkCoin International",
|
||||
"CheckType": "HTML String Check",
|
||||
|
||||
@@ -327,21 +327,6 @@
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "LocalBitcoins",
|
||||
"CheckType": "HTML String Check",
|
||||
"Data": {
|
||||
"HTMLData": {
|
||||
"TokenData": "div",
|
||||
"Key": "class",
|
||||
"Val": "col-md-12",
|
||||
"RegExp": "col-md-12([\\s\\S]*?)clearfix",
|
||||
"CheckString": "37a144dc619776b87c098da5a88bef7fed6c8a7cea2d4b9a38c96750726c93ff",
|
||||
"Path": "https://localbitcoins.com/api-docs/"
|
||||
}
|
||||
},
|
||||
"Disabled": false
|
||||
},
|
||||
{
|
||||
"Name": "OkCoin International",
|
||||
"CheckType": "HTML String Check",
|
||||
|
||||
@@ -63,7 +63,6 @@ _b in this context is an `IBotExchange` implemented struct_
|
||||
| ItBit | Yes | NA | No |
|
||||
| Kraken | Yes | Yes | No |
|
||||
| Lbank | Yes | No | Yes |
|
||||
| LocalBitcoins | Yes | NA | No |
|
||||
| OKCoin International | Yes | Yes | No |
|
||||
| Okx | Yes | Yes | Yes |
|
||||
| Poloniex | Yes | Yes | Yes |
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
{{define "exchanges localbitcoins" -}}
|
||||
{{template "header" .}}
|
||||
## LocalBitcoins Exchange
|
||||
|
||||
### Current Features
|
||||
|
||||
+ REST Support
|
||||
|
||||
### How to enable
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### How to do REST public/private calls
|
||||
|
||||
+ If enabled via "configuration".json file the exchange will be added to the
|
||||
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
|
||||
the wrapper interface functions for accessing exchange data. View routines.go
|
||||
for an example of integration usage with GoCryptoTrader. Rudimentary example
|
||||
below:
|
||||
|
||||
main.go
|
||||
```go
|
||||
var l exchange.IBotExchange
|
||||
|
||||
for i := range bot.Exchanges {
|
||||
if bot.Exchanges[i].GetName() == "LocalBitcoins" {
|
||||
l = bot.Exchanges[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Public calls - wrapper functions
|
||||
|
||||
// Fetches current ticker information
|
||||
tick, err := l.FetchTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := l.FetchOrderbook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
|
||||
// set and AuthenticatedAPISupport is set to true
|
||||
|
||||
// Fetches current account information
|
||||
accountInfo, err := l.GetAccountInfo()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
+ If enabled via individually importing package, rudimentary example below:
|
||||
|
||||
```go
|
||||
// Public calls
|
||||
|
||||
// Fetches current ticker information
|
||||
ticker, err := l.GetTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := l.GetOrderBook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - make sure your APIKEY and APISECRET are set and
|
||||
// AuthenticatedAPISupport is set to true
|
||||
|
||||
// GetUserInfo returns account info
|
||||
accountInfo, err := l.GetUserInfo(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Submits an order and the exchange and returns its tradeID
|
||||
tradeID, err := l.Trade(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -41,7 +41,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| ItBit | Yes | NA | No |
|
||||
| Kraken | Yes | Yes | NA |
|
||||
| Lbank | Yes | No | NA |
|
||||
| LocalBitcoins | Yes | NA | NA |
|
||||
| OKCoin International | Yes | Yes | No |
|
||||
| Okx | Yes | Yes | NA |
|
||||
| Poloniex | Yes | Yes | NA |
|
||||
|
||||
@@ -141,11 +141,6 @@
|
||||
"secret": "Secret",
|
||||
"otpSecret": "-"
|
||||
},
|
||||
"localbitcoins": {
|
||||
"key": "Key",
|
||||
"secret": "Secret",
|
||||
"otpSecret": "-"
|
||||
},
|
||||
"okcoin international": {
|
||||
"key": "Key",
|
||||
"secret": "Secret",
|
||||
|
||||
@@ -57,10 +57,10 @@ func TestZip(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if filepath.Base(o[0]) != "binance.json" || filepath.Base(o[4]) != "localbitcoins.json" {
|
||||
if filepath.Base(o[0]) != "binance.json" {
|
||||
t.Fatal("unexpected archive result received")
|
||||
}
|
||||
if expected := 7; len(o) != expected {
|
||||
if expected := 6; len(o) != expected {
|
||||
t.Fatalf("expected %v files to be extracted received: %v ", expected, len(o))
|
||||
}
|
||||
|
||||
|
||||
@@ -2020,82 +2020,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LocalBitcoins",
|
||||
"enabled": true,
|
||||
"verbose": false,
|
||||
"httpTimeout": 15000000000,
|
||||
"websocketResponseCheckTimeout": 30000000,
|
||||
"websocketResponseMaxLimit": 7000000000,
|
||||
"websocketTrafficTimeout": 30000000000,
|
||||
"websocketOrderbookBufferLimit": 5,
|
||||
"baseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR",
|
||||
"currencyPairs": {
|
||||
"requestFormat": {
|
||||
"uppercase": true
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true
|
||||
},
|
||||
"useGlobalFormat": true,
|
||||
"assetTypes": [
|
||||
"spot"
|
||||
],
|
||||
"pairs": {
|
||||
"spot": {
|
||||
"enabled": "BTCAUD,BTCUSD",
|
||||
"available": "BTCBOB,BTCCZK,BTCUAH,BTCKZT,BTCPAB,BTCPLN,BTCJMD,BTCSZL,BTCILS,BTCMAD,BTCCOP,BTCHRK,BTCEUR,BTCUYU,BTCRSD,BTCPEN,BTCJOD,BTCCHF,BTCKWD,BTCAOA,BTCRUB,BTCVND,BTCRON,BTCHNL,BTCLTC,BTCCLP,BTCNOK,BTCINR,BTCQAR,BTCPKR,BTCTZS,BTCUGX,BTCZMW,BTCXAF,BTCBDT,BTCUSD,BTCDKK,BTCPYG,BTCTHB,BTCSEK,BTCSGD,BTCGEL,BTCBYN,BTCTWD,BTCAED,BTCCAD,BTCAUD,BTCKRW,BTCBRL,BTCCRC,BTCETH,BTCHKD,BTCBWP,BTCXRP,BTCZAR,BTCVES,BTCAMD,BTCSAR,BTCPHP,BTCMVR,BTCGTQ,BTCLKR,BTCCDF,BTCARS,BTCEGP,BTCOMR,BTCNZD,BTCNGN,BTCGBP,BTCMWK,BTCXOF,BTCGHS,BTCDOP,BTCKES,BTCIDR,BTCJPY,BTCNAD,BTCMYR,BTCMUR,BTCCNY,BTCRWF,BTCTRY,BTCMXN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"authenticatedSupport": false,
|
||||
"authenticatedWebsocketApiSupport": false,
|
||||
"endpoints": {
|
||||
"url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
|
||||
},
|
||||
"credentials": {
|
||||
"key": "Key",
|
||||
"secret": "Secret"
|
||||
},
|
||||
"credentialsValidator": {
|
||||
"requiresKey": true,
|
||||
"requiresSecret": true
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"supports": {
|
||||
"restAPI": true,
|
||||
"restCapabilities": {
|
||||
"tickerBatching": true,
|
||||
"autoPairUpdates": true
|
||||
},
|
||||
"websocketAPI": false,
|
||||
"websocketCapabilities": {}
|
||||
},
|
||||
"enabled": {
|
||||
"autoPairUpdates": true,
|
||||
"websocketAPI": false
|
||||
}
|
||||
},
|
||||
"bankAccounts": [
|
||||
{
|
||||
"enabled": false,
|
||||
"bankName": "",
|
||||
"bankAddress": "",
|
||||
"bankPostalCode": "",
|
||||
"bankPostalCity": "",
|
||||
"bankCountry": "",
|
||||
"accountName": "",
|
||||
"accountNumber": "",
|
||||
"swiftCode": "",
|
||||
"iban": "",
|
||||
"supportedCurrencies": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "OKCOIN International",
|
||||
"enabled": true,
|
||||
|
||||
@@ -216,7 +216,6 @@ Yes means supported, No means not yet implemented and NA means protocol unsuppor
|
||||
| ItBit | Yes | NA | No |
|
||||
| Kraken | Yes | Yes | NA |
|
||||
| Lbank | Yes | No | NA |
|
||||
| LocalBitcoins | Yes | NA | NA |
|
||||
| OKCoin International | Yes | Yes | No |
|
||||
| Okx | Yes | Yes | NA |
|
||||
| Poloniex | Yes | Yes | NA |
|
||||
@@ -247,7 +246,6 @@ var Exchanges = []string{
|
||||
"itbit",
|
||||
"kraken",
|
||||
"lbank",
|
||||
"localbitcoins",
|
||||
"okcoin international",
|
||||
"okx",
|
||||
"poloniex",
|
||||
|
||||
@@ -64,7 +64,6 @@ $ ./gctcli withdrawcryptofunds --exchange=binance --currency=USDT --address=TJU9
|
||||
| ItBit | No | No | |
|
||||
| Kraken | Yes | Yes | Front-end and API don't match total available transfer chains |
|
||||
| Lbank | No | No | |
|
||||
| LocalBitcoins | No | No | Supports BTC only |
|
||||
| OKCoin International | No | No | Requires API update to version 5 |
|
||||
| Okx | Yes | Yes | |
|
||||
| Poloniex | Yes | Yes | |
|
||||
|
||||
@@ -85,8 +85,7 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
|
||||
| Huobi | Y |
|
||||
| itBIT | |
|
||||
| Kraken | Y |
|
||||
| lBank | Y |
|
||||
| Localbitcoins | |
|
||||
| lBank | Y |
|
||||
| Okcoin | Y |
|
||||
| Okx | Y |
|
||||
| Poloniex | Y |
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/itbit"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kraken"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/lbank"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/localbitcoins"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okcoin"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okx"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/poloniex"
|
||||
@@ -188,8 +187,6 @@ func (m *ExchangeManager) NewExchangeByName(name string) (exchange.IBotExchange,
|
||||
exch = new(kraken.Kraken)
|
||||
case "lbank":
|
||||
exch = new(lbank.Lbank)
|
||||
case "localbitcoins":
|
||||
exch = new(localbitcoins.LocalBitcoins)
|
||||
case "okcoin international":
|
||||
exch = new(okcoin.OKCoin)
|
||||
case "okx":
|
||||
|
||||
@@ -82,7 +82,7 @@ func TestExchangeManagerRemoveExchange(t *testing.T) {
|
||||
|
||||
func TestNewExchangeByName(t *testing.T) {
|
||||
m := SetupExchangeManager()
|
||||
exchanges := []string{"binanceus", "binance", "bitfinex", "bitflyer", "bithumb", "bitmex", "bitstamp", "bittrex", "btc markets", "btse", "bybit", "coinut", "exmo", "coinbasepro", "gateio", "gemini", "hitbtc", "huobi", "itbit", "kraken", "lbank", "localbitcoins", "okcoin international", "okx", "poloniex", "yobit", "zb", "fake"}
|
||||
exchanges := []string{"binanceus", "binance", "bitfinex", "bitflyer", "bithumb", "bitmex", "bitstamp", "bittrex", "btc markets", "btse", "bybit", "coinut", "exmo", "coinbasepro", "gateio", "gemini", "hitbtc", "huobi", "itbit", "kraken", "lbank", "okcoin international", "okx", "poloniex", "yobit", "zb", "fake"}
|
||||
for i := range exchanges {
|
||||
exch, err := m.NewExchangeByName(exchanges[i])
|
||||
if err != nil && exchanges[i] != "fake" {
|
||||
|
||||
@@ -136,6 +136,9 @@ func (by *Bybit) GetFuturesKlineData(ctx context.Context, symbol currency.Pair,
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
return resp.Data, errInvalidInterval
|
||||
}
|
||||
if startTime.IsZero() {
|
||||
return nil, errInvalidStartTime
|
||||
}
|
||||
params.Set("interval", interval)
|
||||
params.Set("from", strconv.FormatInt(startTime.Unix(), 10))
|
||||
|
||||
|
||||
@@ -592,8 +592,8 @@ func TestGetFuturesKlineData(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetFuturesKlineData(context.Background(), pair, "M", 5, time.Time{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !errors.Is(err, errInvalidStartTime) {
|
||||
t.Errorf("received: %s, expected: %s", err, errInvalidStartTime)
|
||||
}
|
||||
|
||||
_, err = b.GetFuturesKlineData(context.Background(), pair, "60", 5, time.Unix(1577836800, 0))
|
||||
@@ -1230,8 +1230,8 @@ func TestGetUSDTFuturesKlineData(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.GetUSDTFuturesKlineData(context.Background(), pair, "M", 5, time.Time{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !errors.Is(err, errInvalidStartTime) {
|
||||
t.Errorf("received: %s, expected: %s", err, errInvalidStartTime)
|
||||
}
|
||||
|
||||
_, err = b.GetUSDTFuturesKlineData(context.Background(), pair, "60", 5, time.Unix(1577836800, 0))
|
||||
|
||||
@@ -78,6 +78,9 @@ func (by *Bybit) GetUSDTFuturesKlineData(ctx context.Context, symbol currency.Pa
|
||||
if !common.StringDataCompare(validFuturesIntervals, interval) {
|
||||
return resp.Data, errInvalidInterval
|
||||
}
|
||||
if startTime.IsZero() {
|
||||
return nil, errInvalidStartTime
|
||||
}
|
||||
params.Set("interval", interval)
|
||||
params.Set("from", strconv.FormatInt(startTime.Unix(), 10))
|
||||
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
# GoCryptoTrader package Localbitcoins
|
||||
|
||||
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/localbitcoins)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This localbitcoins package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
|
||||
|
||||
## LocalBitcoins Exchange
|
||||
|
||||
### Current Features
|
||||
|
||||
+ REST Support
|
||||
|
||||
### How to enable
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### How to do REST public/private calls
|
||||
|
||||
+ If enabled via "configuration".json file the exchange will be added to the
|
||||
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
|
||||
the wrapper interface functions for accessing exchange data. View routines.go
|
||||
for an example of integration usage with GoCryptoTrader. Rudimentary example
|
||||
below:
|
||||
|
||||
main.go
|
||||
```go
|
||||
var l exchange.IBotExchange
|
||||
|
||||
for i := range bot.Exchanges {
|
||||
if bot.Exchanges[i].GetName() == "LocalBitcoins" {
|
||||
l = bot.Exchanges[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Public calls - wrapper functions
|
||||
|
||||
// Fetches current ticker information
|
||||
tick, err := l.FetchTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := l.FetchOrderbook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
|
||||
// set and AuthenticatedAPISupport is set to true
|
||||
|
||||
// Fetches current account information
|
||||
accountInfo, err := l.GetAccountInfo()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
+ If enabled via individually importing package, rudimentary example below:
|
||||
|
||||
```go
|
||||
// Public calls
|
||||
|
||||
// Fetches current ticker information
|
||||
ticker, err := l.GetTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := l.GetOrderBook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - make sure your APIKEY and APISECRET are set and
|
||||
// AuthenticatedAPISupport is set to true
|
||||
|
||||
// GetUserInfo returns account info
|
||||
accountInfo, err := l.GetUserInfo(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Submits an order and the exchange and returns its tradeID
|
||||
tradeID, err := l.Trade(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
@@ -1,802 +0,0 @@
|
||||
package localbitcoins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
)
|
||||
|
||||
const (
|
||||
localbitcoinsAPIURL = "https://localbitcoins.com"
|
||||
|
||||
// Authenticated Calls
|
||||
localbitcoinsAPIAccountInfo = "api/account_info"
|
||||
localbitcoinsAPIMyself = "myself/"
|
||||
localbitcoinsAPIAds = "ads/"
|
||||
localbitcoinsAPIAdGet = "ad-get/"
|
||||
localbitcoinsAPIAdEdit = "ad/"
|
||||
localbitcoinsAPIAdCreate = "ad-create/"
|
||||
localbitcoinsAPIUpdateEquation = "ad-equation/"
|
||||
localbitcoinsAPIDeleteAd = "ad-delete/"
|
||||
localbitcoinsAPIRelease = "contact_release/"
|
||||
localbitcoinsAPIReleaseByPin = "contact_release_pin/"
|
||||
localbitcoinsAPIMarkAsPaid = "contact_mark_as_paid/"
|
||||
localbitcoinsAPIMessages = "contact_messages/"
|
||||
localbitcoinsAPISendMessage = "contact_message_post/"
|
||||
localbitcoinsAPIDispute = "contact_dispute/"
|
||||
localbitcoinsAPICancelTrade = "contact_cancel/"
|
||||
localbitcoinsAPIFundTrade = "contact_fund/"
|
||||
localbitcoinsAPIConfirmRealName = "contact_mark_realname/"
|
||||
localbitcoinsAPIVerifyIdentity = "contact_mark_identified/"
|
||||
localbitcoinsAPIInitiateTrade = "contact_create/"
|
||||
localbitcoinsAPITradeInfo = "contact_info/"
|
||||
localbitcoinsAPIDashboard = "dashboard/"
|
||||
localbitcoinsAPIDashboardReleased = "dashboard/released/"
|
||||
localbitcoinsAPIDashboardCancelled = "dashboard/canceled/"
|
||||
localbitcoinsAPIDashboardClosed = "dashboard/closed/"
|
||||
localbitcoinsAPIFeedback = "feedback/"
|
||||
localbitcoinsAPILogout = "logout/"
|
||||
localbitcoinsAPICreateInvoice = "merchant/new_invoice/"
|
||||
localbitcoinsAPIGetNotification = "notifications/"
|
||||
localbitcoinsAPIMarkNotification = "notifications/mark_as_read/"
|
||||
localbitcoinsAPIPinCode = "pincode/"
|
||||
localbitcoinsAPIVerifyUsername = "real_name_verifiers/"
|
||||
localbitcoinsAPIWallet = "wallet/"
|
||||
localbitcoinsAPIWalletBalance = "wallet-balance/"
|
||||
localbitcoinsAPIWalletSend = "wallet-send/"
|
||||
localbitcoinsAPIWalletSendPin = "wallet-send-pin/"
|
||||
localbitcoinsAPIWalletAddress = "wallet-addr/"
|
||||
|
||||
// Un-Autheticated Calls
|
||||
localbitcoinsAPICountryCodes = "/api/countrycodes/"
|
||||
localbitcoinsAPICurrencies = "/api/currencies/"
|
||||
localbitcoinsAPIPaymentMethods = "/api/payment_methods/"
|
||||
localbitcoinsAPIPlaces = "/api/places/"
|
||||
localbitcoinsAPITicker = "/bitcoinaverage/ticker-all-currencies/"
|
||||
localbitcoinsAPIBitcoincharts = "/bitcoincharts/"
|
||||
localbitcoinsAPICashBuy = "/buy-bitcoins-with-cash/"
|
||||
localbitcoinsAPIOnlineBuy = "/buy-bitcoins-online/"
|
||||
localbitcoinsAPIOrderbook = "/orderbook.json"
|
||||
localbitcoinsAPITrades = "/trades.json"
|
||||
|
||||
// Trade Types
|
||||
tradeTypeLocalSell = "LOCAL_SELL"
|
||||
tradeTypeLocalBuy = "LOCAL_BUY"
|
||||
tradeTypeOnlineSell = "ONLINE_SELL"
|
||||
tradeTypeOnlineBuy = "ONLINE_BUY"
|
||||
|
||||
// Reference Types
|
||||
refTypeShort = "SHORT"
|
||||
refTypeLong = "LONG"
|
||||
refTypeNumbers = "NUMBERS"
|
||||
refTypeLetters = "LETTERS"
|
||||
|
||||
// Feedback Values
|
||||
feedbackTrust = "trust"
|
||||
feedbackPositive = "positive"
|
||||
feedbackNeutral = "neutral"
|
||||
feedbackBlock = "block"
|
||||
feedbackBlockWithoutFeedback = "block_without_feedback"
|
||||
|
||||
// State Values
|
||||
stateNotOpened = "NOT_OPENED"
|
||||
stateWaitingForPayment = "WAITING_FOR_PAYMENT"
|
||||
statePaid = "PAID"
|
||||
stateNotPaid = "DIDNT_PAID"
|
||||
statePaidLate = "PAID_IN_LATE"
|
||||
statePartlyPaid = "PAID_PARTLY"
|
||||
statePaidAndConfirmed = "PAID_AND_CONFIRMED"
|
||||
statePaidLateConfirmed = "PAID_IN_LATE_AND_CONFIRMED"
|
||||
statePaidPartlyConfirmed = "PAID_PARTLY_AND_CONFIRMED"
|
||||
|
||||
// String response used with order status
|
||||
null = "null"
|
||||
)
|
||||
|
||||
var (
|
||||
// Payment Methods
|
||||
paymentMethodOne string
|
||||
)
|
||||
|
||||
// LocalBitcoins is the overarching type across the localbitcoins package
|
||||
type LocalBitcoins struct {
|
||||
exchange.Base
|
||||
}
|
||||
|
||||
// GetAccountInformation lets you retrieve the public user information on a
|
||||
// LocalBitcoins user. The response contains the same information that is found
|
||||
// on an account's public profile page.
|
||||
func (l *LocalBitcoins) GetAccountInformation(ctx context.Context, username string, self bool) (AccountInfo, error) {
|
||||
type response struct {
|
||||
Data AccountInfo `json:"data"`
|
||||
}
|
||||
resp := response{}
|
||||
|
||||
if self {
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPIMyself, nil, &resp)
|
||||
if err != nil {
|
||||
return resp.Data, err
|
||||
}
|
||||
} else {
|
||||
path := fmt.Sprintf("/%s/%s/", localbitcoinsAPIAccountInfo, username)
|
||||
err := l.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp, request.Unset)
|
||||
if err != nil {
|
||||
return resp.Data, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
// Getads returns information of single advertisement based on the ad ID, if
|
||||
// adID omitted.
|
||||
//
|
||||
// adID - [optional] string if omitted returns all ads
|
||||
func (l *LocalBitcoins) Getads(ctx context.Context, args ...string) (AdData, error) {
|
||||
var resp struct {
|
||||
Data AdData `json:"data"`
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"error_code"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(args) == 0 {
|
||||
err = l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet,
|
||||
localbitcoinsAPIAds,
|
||||
nil,
|
||||
&resp)
|
||||
} else {
|
||||
params := url.Values{"ads": {strings.Join(args, ",")}}
|
||||
err = l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet,
|
||||
localbitcoinsAPIAdGet,
|
||||
params,
|
||||
&resp)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return resp.Data, err
|
||||
}
|
||||
|
||||
if resp.Error.Message != "" {
|
||||
return resp.Data, errors.New(resp.Error.Message)
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
// EditAd updates set advertisements
|
||||
//
|
||||
// params - see localbitcoins_types.go AdEdit for reference
|
||||
// adID - string for the ad you already created
|
||||
// TODO use parameter
|
||||
func (l *LocalBitcoins) EditAd(ctx context.Context, _ *AdEdit, adID string) error {
|
||||
resp := struct {
|
||||
Data AdData `json:"data"`
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"error_code"`
|
||||
}
|
||||
}{}
|
||||
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx,
|
||||
exchange.RestSpot,
|
||||
http.MethodPost,
|
||||
localbitcoinsAPIAdEdit+adID+"/",
|
||||
nil,
|
||||
&resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Error.Message != "" {
|
||||
return errors.New(resp.Error.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAd creates a new advertisement
|
||||
//
|
||||
// params - see localbitcoins_types.go AdCreate for reference
|
||||
// TODO use parameter
|
||||
func (l *LocalBitcoins) CreateAd(ctx context.Context, _ *AdCreate) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIAdCreate, nil, nil)
|
||||
}
|
||||
|
||||
// UpdatePriceEquation updates price equation of an advertisement. If there are
|
||||
// problems with new equation, the price and equation are not updated and
|
||||
// advertisement remains visible.
|
||||
//
|
||||
// equation - string of equation
|
||||
// adID - string of specific ad identification
|
||||
// TODO use parameter
|
||||
func (l *LocalBitcoins) UpdatePriceEquation(ctx context.Context, adID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIUpdateEquation+adID, nil, nil)
|
||||
}
|
||||
|
||||
// DeleteAd deletes the advertisement by adID.
|
||||
//
|
||||
// adID - string of specific ad identification
|
||||
func (l *LocalBitcoins) DeleteAd(ctx context.Context, adID string) error {
|
||||
resp := struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"error_code"`
|
||||
} `json:"error"`
|
||||
}{}
|
||||
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx,
|
||||
exchange.RestSpot,
|
||||
http.MethodPost,
|
||||
localbitcoinsAPIDeleteAd+adID+"/",
|
||||
nil,
|
||||
&resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Error.Message != "" {
|
||||
return errors.New(resp.Error.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseFunds releases Bitcoin trades specified by ID {contact_id}. If the
|
||||
// release was successful a message is returned on the data key.
|
||||
func (l *LocalBitcoins) ReleaseFunds(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIRelease+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// ReleaseFundsByPin releases Bitcoin trades specified by ID {contact_id}. if
|
||||
// the current pincode is provided. If the release was successful a message is
|
||||
// returned on the data key.
|
||||
func (l *LocalBitcoins) ReleaseFundsByPin(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIReleaseByPin+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// MarkAsPaid marks a trade as paid.
|
||||
func (l *LocalBitcoins) MarkAsPaid(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIMarkAsPaid+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// GetMessages returns all chat messages from the trade. Messages are on the message_list key.
|
||||
func (l *LocalBitcoins) GetMessages(ctx context.Context, contactID string) (Message, error) {
|
||||
type response struct {
|
||||
MessageList Message `json:"message_list"`
|
||||
}
|
||||
resp := response{}
|
||||
|
||||
return resp.MessageList,
|
||||
l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIMessages+contactID, nil, &resp)
|
||||
}
|
||||
|
||||
// SendMessage posts a message and/or uploads an image to the trade. Encode
|
||||
// images with multipart/form-data encoding.
|
||||
func (l *LocalBitcoins) SendMessage(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPISendMessage+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// Dispute starts a dispute on the specified trade ID if the requirements for
|
||||
// starting the dispute has been fulfilled.
|
||||
//
|
||||
// topic - [optional] String Short description of issue to LocalBitcoins customer support.
|
||||
// TODO use parameter
|
||||
func (l *LocalBitcoins) Dispute(ctx context.Context, _, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIDispute+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// CancelTrade cancels the trade if the token owner is the Bitcoin buyer.
|
||||
// Bitcoin sellers cannot cancel trades.
|
||||
func (l *LocalBitcoins) CancelTrade(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPICancelTrade+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// FundTrade attempts to fund an unfunded local trade from the token owners
|
||||
// wallet. Works only if the token owner is the Bitcoin seller in the trade.
|
||||
func (l *LocalBitcoins) FundTrade(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIFundTrade+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// ConfirmRealName creates or updates real name confirmation.
|
||||
func (l *LocalBitcoins) ConfirmRealName(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIConfirmRealName+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// VerifyIdentity marks the identity of trade partner as verified. You must be
|
||||
// the advertiser in this trade.
|
||||
func (l *LocalBitcoins) VerifyIdentity(ctx context.Context, contactID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIVerifyIdentity+contactID, nil, nil)
|
||||
}
|
||||
|
||||
// InitiateTrade sttempts to start a Bitcoin trade from the specified
|
||||
// advertisement ID.
|
||||
func (l *LocalBitcoins) InitiateTrade(ctx context.Context, adID string) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIInitiateTrade+adID, nil, nil)
|
||||
}
|
||||
|
||||
// GetTradeInfo returns information about a single trade that the token owner is
|
||||
// part in.
|
||||
func (l *LocalBitcoins) GetTradeInfo(ctx context.Context, contactID string) (dbi DashBoardInfo, err error) {
|
||||
err = l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPITradeInfo+contactID+"/", nil, &dbi)
|
||||
return
|
||||
}
|
||||
|
||||
// GetCountryCodes returns a list of valid and recognized countrycodes
|
||||
func (l *LocalBitcoins) GetCountryCodes(ctx context.Context) error {
|
||||
return l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPICountryCodes, nil, request.Unset)
|
||||
}
|
||||
|
||||
// GetCurrencies returns a list of valid and recognized fiat currencies. Also
|
||||
// contains human readable name for every currency and boolean that tells if
|
||||
// currency is an altcoin.
|
||||
func (l *LocalBitcoins) GetCurrencies(ctx context.Context) error {
|
||||
return l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPICurrencies, nil, request.Unset)
|
||||
}
|
||||
|
||||
// GetDashboardInfo returns a list of trades on the data key contact_list. This
|
||||
// API end point mirrors the website's dashboard, allowing access to contacts in
|
||||
// different states.
|
||||
// In addition all of these listings have buyer/ and seller/ sub-listings to
|
||||
// view contacts where the token owner is either buying or selling, respectively.
|
||||
// E.g. /api/dashboard/buyer/. All contacts where the token owner is
|
||||
// participating are returned.
|
||||
func (l *LocalBitcoins) GetDashboardInfo(ctx context.Context) ([]DashBoardInfo, error) {
|
||||
var resp struct {
|
||||
Data struct {
|
||||
ContactList []DashBoardInfo `json:"contact_list"`
|
||||
ContactCount int `json:"contact_count"`
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Data.ContactList,
|
||||
l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPIDashboard, nil, &resp)
|
||||
}
|
||||
|
||||
// GetDashboardReleasedTrades returns a list of all released trades where the
|
||||
// token owner is either a buyer or seller.
|
||||
func (l *LocalBitcoins) GetDashboardReleasedTrades(ctx context.Context) ([]DashBoardInfo, error) {
|
||||
var resp struct {
|
||||
Data struct {
|
||||
ContactList []DashBoardInfo `json:"contact_list"`
|
||||
ContactCount int `json:"contact_count"`
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Data.ContactList,
|
||||
l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPIDashboardReleased, nil, &resp)
|
||||
}
|
||||
|
||||
// GetDashboardCancelledTrades returns a list of all canceled trades where the
|
||||
// token owner is either a buyer or seller.
|
||||
func (l *LocalBitcoins) GetDashboardCancelledTrades(ctx context.Context) ([]DashBoardInfo, error) {
|
||||
var resp struct {
|
||||
Data struct {
|
||||
ContactList []DashBoardInfo `json:"contact_list"`
|
||||
ContactCount int `json:"contact_count"`
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Data.ContactList,
|
||||
l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPIDashboardCancelled, nil, &resp)
|
||||
}
|
||||
|
||||
// GetDashboardClosedTrades returns a list of all closed trades where the token
|
||||
// owner is either a buyer or seller.
|
||||
func (l *LocalBitcoins) GetDashboardClosedTrades(ctx context.Context) ([]DashBoardInfo, error) {
|
||||
var resp struct {
|
||||
Data struct {
|
||||
ContactList []DashBoardInfo `json:"contact_list"`
|
||||
ContactCount int `json:"contact_count"`
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Data.ContactList,
|
||||
l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPIDashboardClosed, nil, &resp)
|
||||
}
|
||||
|
||||
// SetFeedback gives feedback to user. Possible feedback values are: trust,
|
||||
// positive, neutral, block, block_without_feedback, (check const values)
|
||||
// This is only possible to set if there is a trade between the token owner and
|
||||
// the user specified in {username} that is canceled or released. You may also
|
||||
// set feedback message using msg field with few exceptions. Feedback
|
||||
// block_without_feedback clears the message and with block the message is
|
||||
// mandatory.
|
||||
//
|
||||
// feedback - string (use const valuesfor feedback)
|
||||
// msg - [optional] Feedback message displayed alongside feedback on receivers
|
||||
// profile page.
|
||||
// username - username of trade contact
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) SetFeedback(ctx context.Context) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIFeedback, nil, nil)
|
||||
}
|
||||
|
||||
// Logout expires the current access token immediately. To get a new token
|
||||
// afterwards, public apps will need to re-authenticate, confidential apps can
|
||||
// turn in a refresh token.
|
||||
func (l *LocalBitcoins) Logout(ctx context.Context) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPILogout, nil, nil)
|
||||
}
|
||||
|
||||
// CreateNewInvoice creates a new invoice.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) CreateNewInvoice(ctx context.Context) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPICreateInvoice, nil, nil)
|
||||
}
|
||||
|
||||
// GetInvoice returns information about a specific invoice created by the token
|
||||
// owner.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) GetInvoice(ctx context.Context) (Invoice, error) {
|
||||
resp := Invoice{}
|
||||
return resp, l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPICreateInvoice, nil, &resp)
|
||||
}
|
||||
|
||||
// DeleteInvoice deletes a specific invoice. Deleting invoices is possible when
|
||||
// it is sure that receiver cannot accidentally pay the invoice at the same time
|
||||
// as the merchant is deleting it. You can use the API request
|
||||
// /api/merchant/invoice/{invoice_id}/ to check if deleting is possible.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) DeleteInvoice(ctx context.Context) (Invoice, error) {
|
||||
resp := Invoice{}
|
||||
return resp, l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPICreateInvoice, nil, &resp)
|
||||
}
|
||||
|
||||
// GetNotifications returns recent notifications.
|
||||
func (l *LocalBitcoins) GetNotifications(ctx context.Context) ([]NotificationInfo, error) {
|
||||
var resp []NotificationInfo
|
||||
return resp, l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIGetNotification, nil, &resp)
|
||||
}
|
||||
|
||||
// MarkNotifications marks a specific notification as read.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) MarkNotifications(ctx context.Context) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIMarkNotification, nil, nil)
|
||||
}
|
||||
|
||||
// GetPaymentMethods returns a list of valid payment methods. Also contains name
|
||||
// and code for payment methods, and possible limitations in currencies and bank
|
||||
// name choices.
|
||||
func (l *LocalBitcoins) GetPaymentMethods(ctx context.Context) error {
|
||||
return l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPIPaymentMethods, nil, request.Unset)
|
||||
}
|
||||
|
||||
// GetPaymentMethodsByCountry returns a list of valid payment methods filtered
|
||||
// by countrycodes.
|
||||
func (l *LocalBitcoins) GetPaymentMethodsByCountry(ctx context.Context, countryCode string) error {
|
||||
return l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPIPaymentMethods+countryCode, nil, request.Unset)
|
||||
}
|
||||
|
||||
// CheckPincode checks the given PIN code against the token owners currently
|
||||
// active PIN code. You can use this method to ensure the person using the
|
||||
// session is the legitimate user.
|
||||
// Due to only requiring the read scope, the user is not guaranteed to have set
|
||||
// a PIN code. If you protect your application using this request, please make
|
||||
// the user has set a PIN code for his account.
|
||||
func (l *LocalBitcoins) CheckPincode(ctx context.Context, pin int) (bool, error) {
|
||||
type response struct {
|
||||
Data struct {
|
||||
PinOK bool `json:"pincode_ok"`
|
||||
} `json:"data"`
|
||||
}
|
||||
resp := response{}
|
||||
values := url.Values{}
|
||||
values.Set("pincode", strconv.Itoa(pin))
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIPinCode, values, &resp)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !resp.Data.PinOK {
|
||||
return false, errors.New("pin invalid")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetPlaces Looks up places near lat, lon and provides full URLs to buy and
|
||||
// sell listings for each.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) GetPlaces(ctx context.Context) error {
|
||||
return l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPIPlaces, nil, request.Unset)
|
||||
}
|
||||
|
||||
// VerifyUsername returns list of real name verifiers for the user. Returns a
|
||||
// list only when you have a trade with the user where you are the seller.
|
||||
func (l *LocalBitcoins) VerifyUsername(ctx context.Context) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIVerifyUsername, nil, nil)
|
||||
}
|
||||
|
||||
// GetRecentMessages returns maximum of 25 newest trade messages. Does not
|
||||
// return messages older than one month. Messages are ordered by sending time,
|
||||
// and the newest one is first.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) GetRecentMessages(ctx context.Context) error {
|
||||
return l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIVerifyUsername, nil, nil)
|
||||
}
|
||||
|
||||
// GetWalletInfo gets information about the token owner's wallet balance.
|
||||
func (l *LocalBitcoins) GetWalletInfo(ctx context.Context) (WalletInfo, error) {
|
||||
type response struct {
|
||||
Data WalletInfo `json:"data"`
|
||||
}
|
||||
resp := response{}
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPIWallet, nil, &resp)
|
||||
|
||||
if err != nil {
|
||||
return WalletInfo{}, err
|
||||
}
|
||||
|
||||
if resp.Data.Message != "OK" {
|
||||
return WalletInfo{}, errors.New("unable to fetch wallet info")
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
// GetWalletBalance Same as GetWalletInfo(), but only returns the message,
|
||||
// receiving_address and total fields.
|
||||
// Use this instead if you don't care about transactions at the moment.
|
||||
func (l *LocalBitcoins) GetWalletBalance(ctx context.Context) (WalletBalanceInfo, error) {
|
||||
type response struct {
|
||||
Data WalletBalanceInfo `json:"data"`
|
||||
}
|
||||
resp := response{}
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, localbitcoinsAPIWalletBalance, nil, &resp)
|
||||
|
||||
if err != nil {
|
||||
return WalletBalanceInfo{}, err
|
||||
}
|
||||
|
||||
if resp.Data.Message != "OK" {
|
||||
return WalletBalanceInfo{}, errors.New("unable to fetch wallet balance")
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
// WalletSend sends amount of bitcoins from the token owner's wallet to address.
|
||||
// On success, the response returns a message indicating success. It is highly
|
||||
// recommended to minimize the lifetime of access tokens with the money
|
||||
// permission. Use Logout() to make the current token expire instantly.
|
||||
func (l *LocalBitcoins) WalletSend(ctx context.Context, address string, amount float64, pin int64) error {
|
||||
values := url.Values{}
|
||||
values.Set("address", address)
|
||||
values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
|
||||
path := localbitcoinsAPIWalletSend
|
||||
|
||||
if pin > 0 {
|
||||
values.Set("pincode", strconv.FormatInt(pin, 10))
|
||||
path = localbitcoinsAPIWalletSendPin
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Errors map[string]string `json:"errors"`
|
||||
Code int `json:"error_code"`
|
||||
} `json:"error"`
|
||||
Data struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"data"`
|
||||
}{}
|
||||
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, values, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Data.Message != "Money is being sent" {
|
||||
if len(resp.Error.Errors) != 0 {
|
||||
var details strings.Builder
|
||||
for x := range resp.Error.Errors {
|
||||
details.WriteString(resp.Error.Errors[x])
|
||||
}
|
||||
return errors.New(details.String())
|
||||
}
|
||||
return errors.New(resp.Data.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWalletAddress returns an unused receiving address from the token owner's
|
||||
// wallet. The address is returned in the address key of the response. Note that
|
||||
// this API may keep returning the same (unused) address if requested repeatedly.
|
||||
func (l *LocalBitcoins) GetWalletAddress(ctx context.Context) (string, error) {
|
||||
type response struct {
|
||||
Data struct {
|
||||
Message string `json:"message"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
}
|
||||
resp := response{}
|
||||
err := l.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, localbitcoinsAPIWalletAddress, nil, &resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.Data.Message != "OK!" {
|
||||
return "", errors.New("unable to fetch wallet address")
|
||||
}
|
||||
|
||||
return resp.Data.Address, nil
|
||||
}
|
||||
|
||||
// GetBitcoinsWithCashAd returns buy or sell as cash local advertisements.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) GetBitcoinsWithCashAd(ctx context.Context) error {
|
||||
return l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPICashBuy, nil, request.Unset)
|
||||
}
|
||||
|
||||
// GetBitcoinsOnlineAd this API returns buy or sell Bitcoin online ads.
|
||||
// TODO add support
|
||||
func (l *LocalBitcoins) GetBitcoinsOnlineAd(ctx context.Context) error {
|
||||
return l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPIOnlineBuy, nil, request.Unset)
|
||||
}
|
||||
|
||||
// GetTicker returns list of all completed trades.
|
||||
func (l *LocalBitcoins) GetTicker(ctx context.Context) (map[string]Ticker, error) {
|
||||
result := make(map[string]Ticker)
|
||||
return result, l.SendHTTPRequest(ctx, exchange.RestSpot, localbitcoinsAPITicker, &result, tickerLimiter)
|
||||
}
|
||||
|
||||
// GetTradableCurrencies returns a list of tradable fiat currencies
|
||||
func (l *LocalBitcoins) GetTradableCurrencies(ctx context.Context) ([]string, error) {
|
||||
resp, err := l.GetTicker(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currencies := make([]string, 0, len(resp))
|
||||
for x := range resp {
|
||||
currencies = append(currencies, x)
|
||||
}
|
||||
|
||||
return currencies, nil
|
||||
}
|
||||
|
||||
// GetTrades returns all closed trades in online buy and online sell categories,
|
||||
// updated every 15 minutes.
|
||||
func (l *LocalBitcoins) GetTrades(ctx context.Context, currency string, values url.Values) ([]Trade, error) {
|
||||
endpoint := localbitcoinsAPIBitcoincharts + currency + localbitcoinsAPITrades
|
||||
path := common.EncodeURLValues(endpoint, values)
|
||||
var result []Trade
|
||||
return result, l.SendHTTPRequest(ctx, exchange.RestSpot, path, &result, request.Unset)
|
||||
}
|
||||
|
||||
// GetOrderbook returns buy and sell bitcoin online advertisements. Amount is
|
||||
// the maximum amount available for the trade request. Price is the hourly
|
||||
// updated price. The price is based on the price equation and commission %
|
||||
// entered by the ad author.
|
||||
func (l *LocalBitcoins) GetOrderbook(ctx context.Context, curr string) (*Orderbook, error) {
|
||||
type response struct {
|
||||
Bids [][2]string `json:"bids"`
|
||||
Asks [][2]string `json:"asks"`
|
||||
}
|
||||
|
||||
path := localbitcoinsAPIBitcoincharts + curr + localbitcoinsAPIOrderbook
|
||||
resp := response{}
|
||||
if err := l.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp, orderBookLimiter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ob := Orderbook{
|
||||
Bids: make([]Price, len(resp.Bids)),
|
||||
Asks: make([]Price, len(resp.Asks)),
|
||||
}
|
||||
for x := range resp.Bids {
|
||||
price, err := strconv.ParseFloat(resp.Bids[x][0], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
amount, err := strconv.ParseFloat(resp.Bids[x][1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ob.Bids[x] = Price{price, amount}
|
||||
}
|
||||
|
||||
for x := range resp.Asks {
|
||||
price, err := strconv.ParseFloat(resp.Asks[x][0], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
amount, err := strconv.ParseFloat(resp.Asks[x][1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ob.Asks[x] = Price{price, amount}
|
||||
}
|
||||
|
||||
return &ob, nil
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an unauthenticated HTTP request
|
||||
func (l *LocalBitcoins) SendHTTPRequest(ctx context.Context, endpoint exchange.URL, path string, result interface{}, ep request.EndpointLimit) error {
|
||||
ePoint, err := l.API.Endpoints.GetURL(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item := &request.Item{
|
||||
Method: http.MethodGet,
|
||||
Path: ePoint + path,
|
||||
Result: result,
|
||||
Verbose: l.Verbose,
|
||||
HTTPDebugging: l.HTTPDebugging,
|
||||
HTTPRecording: l.HTTPRecording,
|
||||
}
|
||||
|
||||
return l.SendPayload(ctx, ep, func() (*request.Item, error) {
|
||||
return item, nil
|
||||
})
|
||||
}
|
||||
|
||||
// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to
|
||||
// localbitcoins
|
||||
func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, params url.Values, result interface{}) (err error) {
|
||||
creds, err := l.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint, err := l.API.Endpoints.GetURL(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.SendPayload(ctx, request.Unset, func() (*request.Item, error) {
|
||||
n := l.Requester.GetNonce(true).String()
|
||||
|
||||
fullPath := "/api/" + path
|
||||
encoded := params.Encode()
|
||||
message := n + creds.Key + fullPath + encoded
|
||||
hmac, err := crypto.GetHMAC(crypto.HashSHA256,
|
||||
[]byte(message),
|
||||
[]byte(creds.Secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers := make(map[string]string)
|
||||
headers["Apiauth-Key"] = creds.Key
|
||||
headers["Apiauth-Nonce"] = n
|
||||
headers["Apiauth-Signature"] = strings.ToUpper(crypto.HexEncodeToString(hmac))
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
|
||||
if method == http.MethodGet && len(encoded) > 0 {
|
||||
fullPath += "?" + encoded
|
||||
}
|
||||
|
||||
return &request.Item{
|
||||
Method: method,
|
||||
Path: endpoint + fullPath,
|
||||
Headers: headers,
|
||||
Body: bytes.NewBufferString(encoded),
|
||||
Result: result,
|
||||
AuthRequest: true,
|
||||
NonceEnabled: true,
|
||||
Verbose: l.Verbose,
|
||||
HTTPDebugging: l.HTTPDebugging,
|
||||
HTTPRecording: l.HTTPRecording,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetFee returns an estimate of fee based on type of transaction
|
||||
func (l *LocalBitcoins) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
// No fees will be used
|
||||
return 0, nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
//+build mock_test_off
|
||||
|
||||
// This will build if build tag mock_test_off is parsed and will do live testing
|
||||
// using all tests in (exchange)_test.go
|
||||
package localbitcoins
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
var mockTests = false
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("../../testdata/configtest.json", true)
|
||||
if err != nil {
|
||||
log.Fatal("LocalBitcoins load config error", err)
|
||||
}
|
||||
localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins")
|
||||
if err != nil {
|
||||
log.Fatal("LocalBitcoins Setup() init error", err)
|
||||
}
|
||||
localbitcoinsConfig.API.AuthenticatedSupport = true
|
||||
localbitcoinsConfig.API.Credentials.Key = apiKey
|
||||
localbitcoinsConfig.API.Credentials.Secret = apiSecret
|
||||
l.SetDefaults()
|
||||
err = l.Setup(localbitcoinsConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Localbitcoins setup error", err)
|
||||
}
|
||||
log.Printf(sharedtestvalues.LiveTesting, l.Name)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
//go:build !mock_test_off
|
||||
// +build !mock_test_off
|
||||
|
||||
// This will build if build tag mock_test_off is not parsed and will try to mock
|
||||
// all tests in _test.go
|
||||
package localbitcoins
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/mock"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
const mockfile = "../../testdata/http_mock/localbitcoins/localbitcoins.json"
|
||||
|
||||
var mockTests = true
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("../../testdata/configtest.json", true)
|
||||
if err != nil {
|
||||
log.Fatal("Localbitcoins load config error", err)
|
||||
}
|
||||
localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins")
|
||||
if err != nil {
|
||||
log.Fatal("Localbitcoins Setup() init error", err)
|
||||
}
|
||||
l.SkipAuthCheck = true
|
||||
localbitcoinsConfig.API.AuthenticatedSupport = true
|
||||
localbitcoinsConfig.API.Credentials.Key = apiKey
|
||||
localbitcoinsConfig.API.Credentials.Secret = apiSecret
|
||||
l.SetDefaults()
|
||||
err = l.Setup(localbitcoinsConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Localbitcoins setup error", err)
|
||||
}
|
||||
|
||||
serverDetails, newClient, err := mock.NewVCRServer(mockfile)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock server error %s", err)
|
||||
}
|
||||
|
||||
err = l.SetHTTPClient(newClient)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock server error %s", err)
|
||||
}
|
||||
endpoints := l.API.Endpoints.GetURLMap()
|
||||
for k := range endpoints {
|
||||
err = l.API.Endpoints.SetRunning(k, serverDetails)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf(sharedtestvalues.MockTesting, l.Name)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -1,485 +0,0 @@
|
||||
package localbitcoins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
// Please supply your own APIKEYS here for due diligence testing
|
||||
|
||||
const (
|
||||
apiKey = ""
|
||||
apiSecret = ""
|
||||
canManipulateRealOrders = false
|
||||
)
|
||||
|
||||
var l LocalBitcoins
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := l.Start(nil)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
var testWg sync.WaitGroup
|
||||
err = l.Start(&testWg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testWg.Wait()
|
||||
}
|
||||
|
||||
func TestGetTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := l.GetTicker(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("GetTicker() returned: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTradableCurrencies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := l.GetTradableCurrencies(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("GetTradableCurrencies() returned: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccountInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := l.GetAccountInformation(context.Background(), "", true)
|
||||
switch {
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Could not get AccountInformation: %s", err)
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case mockTests && err != nil:
|
||||
t.Errorf("Could not get AccountInformation: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetads(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := l.Getads(context.Background(), "")
|
||||
switch {
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Could not get ads: %s", err)
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case mockTests && err == nil:
|
||||
t.Error("Expecting error until QA pass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditAd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var edit AdEdit
|
||||
err := l.EditAd(context.Background(), &edit, "1337")
|
||||
switch {
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Could not edit order: %s", err)
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case mockTests && err == nil:
|
||||
t.Error("Expecting error until QA pass")
|
||||
}
|
||||
}
|
||||
|
||||
func setFeeBuilder() *exchange.FeeBuilder {
|
||||
return &exchange.FeeBuilder{
|
||||
Amount: 1,
|
||||
FeeType: exchange.CryptocurrencyTradeFee,
|
||||
Pair: currency.NewPairWithDelimiter(currency.LTC.String(),
|
||||
currency.BTC.String(),
|
||||
"-"),
|
||||
PurchasePrice: 1,
|
||||
FiatCurrency: currency.USD,
|
||||
BankTransactionType: exchange.WireTransfer,
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := l.GetTrades(context.Background(), "LTC", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderbook(t *testing.T) {
|
||||
t.Parallel()
|
||||
ob, err := l.GetOrderbook(context.Background(), "AUD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mockTests {
|
||||
if ob.Bids[0].Price != 88781.42 && ob.Bids[0].Amount != 10000.00 ||
|
||||
ob.Asks[0].Price != 14400.00 && ob.Asks[0].Amount != 0.77 {
|
||||
t.Error("incorrect orderbook values")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetFeeByTypeOfflineTradeFee logic test
|
||||
func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
var feeBuilder = setFeeBuilder()
|
||||
_, err := l.GetFeeByType(context.Background(), feeBuilder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !areTestAPIKeysSet() {
|
||||
if feeBuilder.FeeType != exchange.OfflineTradeFee {
|
||||
t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType)
|
||||
}
|
||||
} else {
|
||||
if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee {
|
||||
t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
var feeBuilder = setFeeBuilder()
|
||||
// CryptocurrencyTradeFee Basic
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// CryptocurrencyTradeFee High quantity
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.Amount = 1000
|
||||
feeBuilder.PurchasePrice = 1000
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// CryptocurrencyTradeFee IsMaker
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.IsMaker = true
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// CryptocurrencyTradeFee Negative purchase price
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.PurchasePrice = -1000
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// CryptocurrencyWithdrawalFee Basic
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// CryptocurrencyWithdrawalFee Invalid currency
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.Pair.Base = currency.NewCode("hello")
|
||||
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// CryptocurrencyDepositFee Basic
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.FeeType = exchange.CryptocurrencyDepositFee
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// InternationalBankDepositFee Basic
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.FeeType = exchange.InternationalBankDepositFee
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// InternationalBankWithdrawalFee Basic
|
||||
feeBuilder = setFeeBuilder()
|
||||
feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee
|
||||
feeBuilder.FiatCurrency = currency.USD
|
||||
if _, err := l.GetFee(feeBuilder); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expectedResult := exchange.AutoWithdrawCryptoText +
|
||||
" & " +
|
||||
exchange.WithdrawFiatViaWebsiteOnlyText
|
||||
withdrawPermissions := l.FormatWithdrawPermissions()
|
||||
if withdrawPermissions != expectedResult {
|
||||
t.Errorf("Expected: %s, Received: %s",
|
||||
expectedResult,
|
||||
withdrawPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
Type: order.AnyType,
|
||||
AssetType: asset.Spot,
|
||||
Side: order.AnySide,
|
||||
}
|
||||
|
||||
_, err := l.GetActiveOrders(context.Background(), &getOrdersRequest)
|
||||
switch {
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Could not get active orders: %s", err)
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case mockTests && err != nil:
|
||||
t.Errorf("Could not get active orders: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
Type: order.AnyType,
|
||||
AssetType: asset.Spot,
|
||||
Side: order.AnySide,
|
||||
}
|
||||
|
||||
_, err := l.GetOrderHistory(context.Background(), &getOrdersRequest)
|
||||
switch {
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Could not get order history: %s", err)
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case mockTests && err != nil:
|
||||
t.Errorf("Could not get order history: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them
|
||||
// ----------------------------------------------------------------------------------------------------------------------------
|
||||
func areTestAPIKeysSet() bool {
|
||||
return l.ValidateAPICredentials(l.GetDefaultCredentials()) == nil
|
||||
}
|
||||
|
||||
func TestSubmitOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
|
||||
var orderSubmission = &order.Submit{
|
||||
Exchange: l.Name,
|
||||
Pair: currency.Pair{
|
||||
Base: currency.BTC,
|
||||
Quote: currency.EUR,
|
||||
},
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
ClientID: "meowOrder",
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
response, err := l.SubmitOrder(context.Background(), orderSubmission)
|
||||
switch {
|
||||
case areTestAPIKeysSet() && (err != nil || response.Status != order.New) && !mockTests:
|
||||
t.Errorf("Order failed to be placed: %v", err)
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case mockTests && err == nil:
|
||||
t.Error("Expecting an error until QA pass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelExchangeOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
|
||||
err := l.CancelOrder(context.Background(), orderCancellation)
|
||||
switch {
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Could not cancel orders: %v", err)
|
||||
case mockTests && err == nil:
|
||||
t.Error("Expecting an error until QA pass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
var orderCancellation = &order.Cancel{
|
||||
OrderID: "1",
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
Pair: currency.NewPair(currency.LTC, currency.BTC),
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
|
||||
resp, err := l.CancelAllOrders(context.Background(), orderCancellation)
|
||||
switch {
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Could not cancel orders: %v", err)
|
||||
case mockTests && err != nil:
|
||||
t.Errorf("Could not cancel orders: %v", err)
|
||||
}
|
||||
|
||||
if len(resp.Status) > 0 {
|
||||
t.Errorf("%v orders failed to cancel", len(resp.Status))
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := l.ModifyOrder(context.Background(),
|
||||
&order.Modify{AssetType: asset.Spot})
|
||||
if err != common.ErrFunctionNotSupported {
|
||||
t.Error("ModifyOrder() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithdraw(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
withdrawCryptoRequest := withdraw.Request{
|
||||
Exchange: l.Name,
|
||||
Amount: -1,
|
||||
Currency: currency.BTC,
|
||||
Description: "WITHDRAW IT ALL",
|
||||
Crypto: withdraw.CryptoRequest{
|
||||
Address: core.BitcoinDonationAddress,
|
||||
},
|
||||
}
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
}
|
||||
|
||||
_, err := l.WithdrawCryptocurrencyFunds(context.Background(),
|
||||
&withdrawCryptoRequest)
|
||||
switch {
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Errorf("Withdraw failed to be placed: %v", err)
|
||||
case mockTests && err == nil:
|
||||
t.Error("Expecting an error until QA pass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithdrawFiat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var withdrawFiatRequest = withdraw.Request{}
|
||||
_, err := l.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest)
|
||||
if err != common.ErrFunctionNotSupported {
|
||||
t.Errorf("Expected '%v', received: '%v'",
|
||||
common.ErrFunctionNotSupported,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithdrawInternationalBank(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var withdrawFiatRequest = withdraw.Request{}
|
||||
_, err := l.WithdrawFiatFundsToInternationalBank(context.Background(), &withdrawFiatRequest)
|
||||
if err != common.ErrFunctionNotSupported {
|
||||
t.Errorf("Expected '%v', received: '%v'",
|
||||
common.ErrFunctionNotSupported,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDepositAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := l.GetDepositAddress(context.Background(), currency.BTC, "", "")
|
||||
switch {
|
||||
case areTestAPIKeysSet() && err != nil && !mockTests:
|
||||
t.Error("GetDepositAddress() error", err)
|
||||
case !areTestAPIKeysSet() && err == nil && !mockTests:
|
||||
t.Error("GetDepositAddress() expecting an error when no APIKeys are set")
|
||||
case mockTests && err != nil:
|
||||
t.Error("GetDepositAddress() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRecentTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
currencyPair, err := currency.NewPairFromString("BTC-LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = l.GetRecentTrades(context.Background(), currencyPair, asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
currencyPair, err := currency.NewPairFromString("BTC-LTC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = l.GetHistoricTrades(context.Background(),
|
||||
currencyPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now())
|
||||
if err != nil && err != common.ErrFunctionNotSupported {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
cp, err := currency.NewPairFromString("BTCUSD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = l.UpdateTicker(context.Background(), cp, asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTickers(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := l.UpdateTickers(context.Background(), asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
package localbitcoins
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// GeneralError is an error capture type
|
||||
type GeneralError struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
ErrorCode int `json:"error_code"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
// AccountInfo holds public user information
|
||||
type AccountInfo struct {
|
||||
Username string `json:"username"`
|
||||
FeedbackScore int `json:"feedback_score"`
|
||||
FeedbackCount int `json:"feedback_count"`
|
||||
RealNameVeriTrusted int `json:"real_name_verifications_trusted"`
|
||||
TradingPartners int `json:"trading_partners_count"`
|
||||
URL string `json:"url"`
|
||||
RealNameVeriUntrusted int `json:"real_name_verifications_untrusted"`
|
||||
HasFeedback bool `json:"has_feedback"`
|
||||
IdentityVerifiedAt string `json:"identify_verified_at"`
|
||||
TrustedCount int `json:"trusted_count"`
|
||||
FeedbacksUnconfirmed int `json:"feedbacks_unconfirmed_count"`
|
||||
BlockedCount int `json:"blocked_count"`
|
||||
TradeVolumeText string `json:"trade_volume_text"`
|
||||
HasCommonTrades bool `json:"has_common_trades"`
|
||||
RealNameVeriRejected int `json:"real_name_verifications_rejected"`
|
||||
AgeText string `json:"age_text"`
|
||||
ConfirmedTradesText string `json:"confirmed_trade_count_text"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// AdData references the full possible return of ad data
|
||||
type AdData struct {
|
||||
AdList []struct {
|
||||
Data struct {
|
||||
Visible bool `json:"visible"`
|
||||
HiddenByOpeningHours bool `json:"hidden_by_opening_hours"`
|
||||
Location string `json:"location_string"`
|
||||
CountryCode string `json:"countrycode"`
|
||||
City string `json:"city"`
|
||||
TradeType string `json:"trade_type"`
|
||||
OnlineProvider string `json:"online_provider"`
|
||||
FirstTimeLimitBTC string `json:"first_time_limit_btc"`
|
||||
VolumeCoefficientBTC string `json:"volume_coefficient_btc"`
|
||||
SMSVerficationRequired bool `json:"sms_verification_required"`
|
||||
ReferenceType string `json:"reference_type"`
|
||||
DisplayReference bool `json:"display_reference"`
|
||||
Currency string `json:"currency"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lon float64 `json:"lon"`
|
||||
MinAmount string `json:"min_amount"`
|
||||
MaxAmount string `json:"max_amount"`
|
||||
MaXAmountAvailable string `json:"max_amount_available"`
|
||||
LimitToFiatAmounts string `json:"limit_to_fiat_amounts"`
|
||||
AdID int64 `json:"ad_id"`
|
||||
TempPriceUSD string `json:"temp_price_usd"`
|
||||
Floating bool `json:"floating"`
|
||||
Profile interface{} `json:"profile"`
|
||||
RequireFeedBackScore int `json:"require_feedback_score"`
|
||||
RequireTradeVolume float64 `json:"require_trade_volume"`
|
||||
RequireTrustedByAdvertiser bool `json:"require_trusted_by_advertiser"`
|
||||
PaymentWindowMinutes int `json:"payment_window_minutes"`
|
||||
BankName string `json:"bank_name"`
|
||||
TrackMaxAmount bool `json:"track_max_amount"`
|
||||
ATMModel string `json:"atm_model"`
|
||||
PriceEquation string `json:"price_equation"`
|
||||
OpeningHours interface{} `json:"opening_hours"`
|
||||
AccountInfo string `json:"account_info"`
|
||||
AccountDetails interface{} `json:"account_details"`
|
||||
} `json:"data"`
|
||||
Actions struct {
|
||||
PublicView string `json:"public_view"`
|
||||
HTMLEdit string `json:"html_edit"`
|
||||
ChangeForm string `json:"change_form"`
|
||||
ContactForm string `json:"contact_form"`
|
||||
} `json:"actions"`
|
||||
} `json:"ad_list"`
|
||||
AdCount int `json:"ad_count"`
|
||||
}
|
||||
|
||||
// AdEdit references an outgoing parameter type for EditAd() method
|
||||
type AdEdit struct {
|
||||
// Required Arguments
|
||||
PriceEquation string `json:"price_equation"`
|
||||
Latitude int `json:"lat"`
|
||||
Longitude int `json:"lon"`
|
||||
City string `json:"city"`
|
||||
Location string `json:"location_string"`
|
||||
CountryCode string `json:"countrycode"`
|
||||
Currency string `json:"currency"`
|
||||
AccountInfo string `json:"account_info"`
|
||||
BankName string `json:"bank_name"`
|
||||
MSG string `json:"msg"`
|
||||
SMSVerficationRequired bool `json:"sms_verification_required"`
|
||||
TrackMaxAmount bool `json:"track_max_amount"`
|
||||
RequireTrustedByAdvertiser bool `json:"require_trusted_by_advertiser"`
|
||||
RequireIdentification bool `json:"require_identification"`
|
||||
|
||||
// Optional Arguments
|
||||
MinAmount int `json:"min_amount"`
|
||||
MaxAmount int `json:"max_amount"`
|
||||
OpeningHours []string `json:"opening_hours"`
|
||||
LimitToFiatAmounts string `json:"limit_to_fiat_amounts"`
|
||||
Visible bool `json:"visible"`
|
||||
|
||||
// Optional Arguments ONLINE_SELL ads
|
||||
RequireTradeVolume int `json:"require_trade_volume"`
|
||||
RequireFeedBackScore int `json:"require_feedback_score"`
|
||||
FirstTimeLimitBTC int `json:"first_time_limit_btc"`
|
||||
VolumeCoefficientBTC int `json:"volume_coefficient_btc"`
|
||||
ReferenceType string `json:"reference_type"`
|
||||
DisplayReference bool `json:"display_reference"`
|
||||
|
||||
// Optional Arguments ONLINE_BUY
|
||||
PaymentWindowMinutes int `json:"payment_window_minutes"`
|
||||
|
||||
// Optional Arguments LOCAL_SELL
|
||||
Floating bool `json:"floating"`
|
||||
}
|
||||
|
||||
// AdCreate references an outgoing parameter type for CreateAd() method
|
||||
type AdCreate struct {
|
||||
// Required Arguments
|
||||
PriceEquation string `json:"price_equation"`
|
||||
Latitude int `json:"lat"`
|
||||
Longitude int `json:"lon"`
|
||||
City string `json:"city"`
|
||||
Location string `json:"location_string"`
|
||||
CountryCode string `json:"countrycode"`
|
||||
Currency string `json:"currency"`
|
||||
AccountInfo string `json:"account_info"`
|
||||
BankName string `json:"bank_name"`
|
||||
MSG string `json:"msg"`
|
||||
SMSVerficationRequired bool `json:"sms_verification_required"`
|
||||
TrackMaxAmount bool `json:"track_max_amount"`
|
||||
RequireTrustedByAdvertiser bool `json:"require_trusted_by_advertiser"`
|
||||
RequireIdentification bool `json:"require_identification"`
|
||||
OnlineProvider string `json:"online_provider"`
|
||||
TradeType string `json:"trade_type"`
|
||||
|
||||
// Optional Arguments
|
||||
MinAmount int `json:"min_amount"`
|
||||
MaxAmount int `json:"max_amount"`
|
||||
OpeningHours []string `json:"opening_hours"`
|
||||
LimitToFiatAmounts string `json:"limit_to_fiat_amounts"`
|
||||
Visible bool `json:"visible"`
|
||||
|
||||
// Optional Arguments ONLINE_SELL ads
|
||||
RequireTradeVolume int `json:"require_trade_volume"`
|
||||
RequireFeedBackScore int `json:"require_feedback_score"`
|
||||
FirstTimeLimitBTC int `json:"first_time_limit_btc"`
|
||||
VolumeCoefficientBTC int `json:"volume_coefficient_btc"`
|
||||
ReferenceType string `json:"reference_type"`
|
||||
DisplayReference bool `json:"display_reference"`
|
||||
|
||||
// Optional Arguments ONLINE_BUY
|
||||
PaymentWindowMinutes int `json:"payment_window_minutes"`
|
||||
|
||||
// Optional Arguments LOCAL_SELL
|
||||
Floating bool `json:"floating"`
|
||||
}
|
||||
|
||||
// Message holds the returned message data from a contact
|
||||
type Message struct {
|
||||
MSG string `json:"msg"`
|
||||
Sender struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
TradeCount int64 `json:"trafe_count"`
|
||||
LastOnline string `json:"last_online"`
|
||||
} `json:"sender"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
AttachmentName string `json:"attachment_name"`
|
||||
AttachmentType string `json:"attachment_type"`
|
||||
AttachmentURL string `json:"attachment_url"`
|
||||
}
|
||||
|
||||
// DashBoardInfo holds the full range of metadata for a dashboard image
|
||||
type DashBoardInfo struct {
|
||||
Data struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
Buyer struct {
|
||||
Username string `json:"username"`
|
||||
TradeCount string `json:"trade_count"`
|
||||
FeedbackScore int `json:"feedback_score"`
|
||||
Name string `json:"name"`
|
||||
LastOnline string `json:"last_online"`
|
||||
RealName string `json:"real_name"`
|
||||
CompanyName string `json:"company_name"`
|
||||
CountryCodeByIP string `json:"countrycode_by_ip"`
|
||||
CountryCodeByPhoneNUmber string `json:"countrycode_by_phone_number"`
|
||||
} `json:"buyer"`
|
||||
Seller struct {
|
||||
Username string `json:"username"`
|
||||
TradeCount string `json:"trade_count"`
|
||||
FeedbackScore int `json:"feedback_score"`
|
||||
Name string `json:"name"`
|
||||
LastOnline string `json:"last_online"`
|
||||
} `json:"seller"`
|
||||
ReferenceCode string `json:"reference_code"`
|
||||
Currency string `json:"currency"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
AmountBTC float64 `json:"amount_btc,string"`
|
||||
FeeBTC float64 `json:"fee_btc,string"`
|
||||
ExchangeRateUpdatedAt string `json:"exchange_rate_updated_at"`
|
||||
Advertisement struct {
|
||||
ID int64 `json:"id"`
|
||||
TradeType string `json:"trade_type"`
|
||||
Advertiser struct {
|
||||
Username string `json:"username"`
|
||||
TradeCount string `json:"trade_count"`
|
||||
FeedbackScore int `json:"feedback_score"`
|
||||
Name string `json:"name"`
|
||||
LastOnline string `json:"last_online"`
|
||||
} `json:"advertiser"`
|
||||
} `json:"advertisement"`
|
||||
ContactID int `json:"contact_id"`
|
||||
CanceledAt string `json:"canceled_at"`
|
||||
EscrowedAt string `json:"escrowed_at"`
|
||||
FundedAt string `json:"funded_at"`
|
||||
PaymentCompletedAt string `json:"payment_completed_at"`
|
||||
DisputedAt string `json:"disputed_at"`
|
||||
ClosedAt string `json:"closed_at"`
|
||||
ReleasedAt string `json:"released_at"`
|
||||
IsBuying bool `json:"is_buying"`
|
||||
IsSelling bool `json:"is_selling"`
|
||||
AccountDetails interface{} `json:"account_details"`
|
||||
AccountInfo string `json:"account_info"`
|
||||
Floating bool `json:"floating"`
|
||||
} `json:"data"`
|
||||
Actions struct {
|
||||
MarkAsPaidURL string `json:"mark_as_paid_url"`
|
||||
AdvertisementPublicView string `json:"advertisement_public_view"`
|
||||
MessageURL string `json:"message_url"`
|
||||
MessagePostURL string `json:"message_post_url"`
|
||||
} `json:"actions"`
|
||||
}
|
||||
|
||||
// Invoice contains invoice data
|
||||
type Invoice struct {
|
||||
Invoice struct {
|
||||
Description string `json:"description"`
|
||||
Created string `json:"created"`
|
||||
URL string `json:"url"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Internal bool `json:"internal"`
|
||||
Currency string `json:"currency"`
|
||||
State string `json:"state"`
|
||||
ID string `json:"id"`
|
||||
BTCAmount string `json:"btc_amount"`
|
||||
BTCAddress string `json:"btc_address"`
|
||||
DeletingAllowed bool `json:"deleting_allowed"`
|
||||
} `json:"invoice"`
|
||||
}
|
||||
|
||||
// NotificationInfo holds Notification data
|
||||
type NotificationInfo struct {
|
||||
URL string `json:"url"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ContactID int64 `json:"contact_id"`
|
||||
Read bool `json:"read"`
|
||||
MSG string `json:"msg"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// WalletInfo holds full wallet information data
|
||||
type WalletInfo struct {
|
||||
Message string `json:"message"`
|
||||
Total Balance `json:"total"`
|
||||
SentTransactions30d []WalletTransaction `json:"sent_transactions_30d"`
|
||||
ReceivedTransactions30d []WalletTransaction `json:"received_transactions_30d"`
|
||||
ReceivingAddressCount int `json:"receiving_address_count"`
|
||||
ReceivingAddressList []WalletAddressList `json:"receiving_address_list"`
|
||||
}
|
||||
|
||||
// Balance is a sub-type for WalletInfo & WalletBalanceInfo
|
||||
type Balance struct {
|
||||
Balance float64 `json:"balance,string"`
|
||||
Sendable float64 `json:"Sendable,string"`
|
||||
}
|
||||
|
||||
// WalletTransaction is a sub-type for WalletInfo
|
||||
type WalletTransaction struct {
|
||||
TXID string `json:"txid"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Description string `json:"description"`
|
||||
TXType int `json:"tx_type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// WalletAddressList is a sub-type for WalletInfo & WalletBalanceInfo
|
||||
type WalletAddressList struct {
|
||||
Address string `json:"address"`
|
||||
Received float64 `json:"received,string"`
|
||||
}
|
||||
|
||||
// WalletBalanceInfo standard wallet balance information
|
||||
type WalletBalanceInfo struct {
|
||||
Message string `json:"message"`
|
||||
Total Balance `json:"total"`
|
||||
ReceivingAddressCount int `json:"receiving_address_count"` // always 1
|
||||
ReceivingAddressList []WalletAddressList `json:"receiving_address_list"`
|
||||
}
|
||||
|
||||
// Ticker contains ticker information
|
||||
type Ticker struct {
|
||||
Avg12h float64 `json:"avg_12h,string"`
|
||||
Avg1h float64 `json:"avg_1h,string,omitempty"`
|
||||
Avg6h float64 `json:"avg_6h,string,omitempty"`
|
||||
Avg24h float64 `json:"avg_24h,string"`
|
||||
Rates struct {
|
||||
Last float64 `json:"last,string"`
|
||||
} `json:"rates"`
|
||||
VolumeBTC float64 `json:"volume_btc,string"`
|
||||
}
|
||||
|
||||
// Trade holds closed trade information
|
||||
type Trade struct {
|
||||
TID int64 `json:"tid"`
|
||||
Date int64 `json:"date"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
}
|
||||
|
||||
// Orderbook is a full range of bid and asks for localbitcoins
|
||||
type Orderbook struct {
|
||||
Bids []Price `json:"bids"`
|
||||
Asks []Price `json:"asks"`
|
||||
}
|
||||
|
||||
// Price is a sub-type for orderbook
|
||||
type Price struct {
|
||||
Price float64
|
||||
Amount float64
|
||||
}
|
||||
@@ -1,709 +0,0 @@
|
||||
package localbitcoins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
)
|
||||
|
||||
// GetDefaultConfig returns a default exchange config
|
||||
func (l *LocalBitcoins) GetDefaultConfig() (*config.Exchange, error) {
|
||||
l.SetDefaults()
|
||||
exchCfg := new(config.Exchange)
|
||||
exchCfg.Name = l.Name
|
||||
exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout
|
||||
exchCfg.BaseCurrencies = l.BaseCurrencies
|
||||
|
||||
err := l.SetupDefaults(exchCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l.Features.Supports.RESTCapabilities.AutoPairUpdates {
|
||||
err = l.UpdateTradablePairs(context.TODO(), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return exchCfg, nil
|
||||
}
|
||||
|
||||
// SetDefaults sets the package defaults for localbitcoins
|
||||
func (l *LocalBitcoins) SetDefaults() {
|
||||
l.Name = "LocalBitcoins"
|
||||
l.Enabled = true
|
||||
l.Verbose = true
|
||||
l.API.CredentialsValidator.RequiresKey = true
|
||||
l.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true}
|
||||
configFmt := ¤cy.PairFormat{
|
||||
Uppercase: true,
|
||||
Delimiter: currency.DashDelimiter}
|
||||
err := l.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
|
||||
l.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
Websocket: false,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
AutoPairUpdates: true,
|
||||
AccountInfo: true,
|
||||
GetOrder: true,
|
||||
CancelOrder: true,
|
||||
SubmitOrder: true,
|
||||
DepositHistory: true,
|
||||
WithdrawalHistory: true,
|
||||
UserTradeHistory: true,
|
||||
CryptoDeposit: true,
|
||||
CryptoWithdrawal: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.WithdrawFiatViaWebsiteOnly,
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
},
|
||||
}
|
||||
|
||||
l.Requester, err = request.New(l.Name,
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
l.API.Endpoints = l.NewEndpoints()
|
||||
err = l.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{
|
||||
exchange.RestSpot: localbitcoinsAPIURL,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup sets exchange configuration parameters
|
||||
func (l *LocalBitcoins) Setup(exch *config.Exchange) error {
|
||||
if err := exch.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !exch.Enabled {
|
||||
l.SetEnabled(false)
|
||||
return nil
|
||||
}
|
||||
return l.SetupDefaults(exch)
|
||||
}
|
||||
|
||||
// Start starts the LocalBitcoins go routine
|
||||
func (l *LocalBitcoins) Start(wg *sync.WaitGroup) error {
|
||||
if wg == nil {
|
||||
return fmt.Errorf("%T %w", wg, common.ErrNilPointer)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
l.Run()
|
||||
wg.Done()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run implements the LocalBitcoins wrapper
|
||||
func (l *LocalBitcoins) Run() {
|
||||
if l.Verbose {
|
||||
l.PrintEnabledPairs()
|
||||
}
|
||||
|
||||
if !l.GetEnabledFeatures().AutoPairUpdates {
|
||||
return
|
||||
}
|
||||
|
||||
err := l.UpdateTradablePairs(context.TODO(), false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", l.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (l *LocalBitcoins) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) {
|
||||
currencies, err := l.GetTradableCurrencies(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pairs := make([]currency.Pair, len(currencies))
|
||||
for x := range currencies {
|
||||
var pair currency.Pair
|
||||
pair, err = currency.NewPairFromStrings("BTC", currencies[x])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pairs[x] = pair
|
||||
}
|
||||
return pairs, nil
|
||||
}
|
||||
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
// them in the exchanges config
|
||||
func (l *LocalBitcoins) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error {
|
||||
pairs, err := l.FetchTradablePairs(ctx, asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.UpdatePairs(pairs, asset.Spot, false, forceUpdate)
|
||||
}
|
||||
|
||||
// UpdateTickers updates the ticker for all currency pairs of a given asset type
|
||||
func (l *LocalBitcoins) UpdateTickers(ctx context.Context, a asset.Item) error {
|
||||
tick, err := l.GetTicker(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pairs, err := l.GetEnabledPairs(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range pairs {
|
||||
curr := pairs[i].Quote.String()
|
||||
if _, ok := tick[curr]; !ok {
|
||||
continue
|
||||
}
|
||||
var tp ticker.Price
|
||||
tp.Pair = pairs[i]
|
||||
tp.Last = tick[curr].Avg24h
|
||||
tp.Volume = tick[curr].VolumeBTC
|
||||
tp.ExchangeName = l.Name
|
||||
tp.AssetType = a
|
||||
|
||||
err = ticker.ProcessTicker(&tp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (l *LocalBitcoins) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) {
|
||||
if err := l.UpdateTickers(ctx, a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ticker.GetTicker(l.Name, p, a)
|
||||
}
|
||||
|
||||
// FetchTicker returns the ticker for a currency pair
|
||||
func (l *LocalBitcoins) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
tickerNew, err := ticker.GetTicker(l.Name, p, assetType)
|
||||
if err != nil {
|
||||
return l.UpdateTicker(ctx, p, assetType)
|
||||
}
|
||||
return tickerNew, nil
|
||||
}
|
||||
|
||||
// FetchOrderbook returns orderbook base on the currency pair
|
||||
func (l *LocalBitcoins) FetchOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
ob, err := orderbook.Get(l.Name, p, assetType)
|
||||
if err != nil {
|
||||
return l.UpdateOrderbook(ctx, p, assetType)
|
||||
}
|
||||
return ob, nil
|
||||
}
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (l *LocalBitcoins) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
|
||||
book := &orderbook.Base{
|
||||
Exchange: l.Name,
|
||||
Pair: p,
|
||||
Asset: assetType,
|
||||
VerifyOrderbook: l.CanVerifyOrderbook,
|
||||
}
|
||||
|
||||
orderbookNew, err := l.GetOrderbook(ctx, p.Quote.String())
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
book.Bids = make(orderbook.Items, len(orderbookNew.Bids))
|
||||
for x := range orderbookNew.Bids {
|
||||
if orderbookNew.Bids[x].Price == 0 {
|
||||
return book, errors.New("orderbook bid price is 0")
|
||||
}
|
||||
book.Bids[x] = orderbook.Item{
|
||||
Amount: orderbookNew.Bids[x].Amount / orderbookNew.Bids[x].Price,
|
||||
Price: orderbookNew.Bids[x].Price,
|
||||
}
|
||||
}
|
||||
|
||||
book.Asks = make(orderbook.Items, len(orderbookNew.Asks))
|
||||
for x := range orderbookNew.Asks {
|
||||
if orderbookNew.Asks[x].Price == 0 {
|
||||
return book, errors.New("orderbook ask price is 0")
|
||||
}
|
||||
book.Asks[x] = orderbook.Item{
|
||||
Amount: orderbookNew.Asks[x].Amount / orderbookNew.Asks[x].Price,
|
||||
Price: orderbookNew.Asks[x].Price,
|
||||
}
|
||||
}
|
||||
|
||||
book.PriceDuplication = true
|
||||
err = book.Process()
|
||||
if err != nil {
|
||||
return book, err
|
||||
}
|
||||
|
||||
return orderbook.Get(l.Name, p, assetType)
|
||||
}
|
||||
|
||||
// UpdateAccountInfo retrieves balances for all enabled currencies for the
|
||||
// LocalBitcoins exchange
|
||||
func (l *LocalBitcoins) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
|
||||
var response account.Holdings
|
||||
response.Exchange = l.Name
|
||||
accountBalance, err := l.GetWalletBalance(ctx)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
response.Accounts = append(response.Accounts, account.SubAccount{
|
||||
AssetType: assetType,
|
||||
Currencies: []account.Balance{
|
||||
{
|
||||
Currency: currency.BTC,
|
||||
Total: accountBalance.Total.Balance,
|
||||
Hold: accountBalance.Total.Balance - accountBalance.Total.Sendable,
|
||||
Free: accountBalance.Total.Sendable,
|
||||
}},
|
||||
})
|
||||
|
||||
creds, err := l.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
err = account.Process(&response, creds)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// FetchAccountInfo retrieves balances for all enabled currencies
|
||||
func (l *LocalBitcoins) FetchAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
|
||||
creds, err := l.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return account.Holdings{}, err
|
||||
}
|
||||
acc, err := account.GetHoldings(l.Name, creds, assetType)
|
||||
if err != nil {
|
||||
return l.UpdateAccountInfo(ctx, assetType)
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// GetFundingHistory returns funding history, deposits and
|
||||
// withdrawals
|
||||
func (l *LocalBitcoins) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWithdrawalsHistory returns previous withdrawals data
|
||||
func (l *LocalBitcoins) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetRecentTrades returns the most recent trades for a currency and asset
|
||||
func (l *LocalBitcoins) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
|
||||
var err error
|
||||
p, err = l.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tradeData []Trade
|
||||
tradeData, err = l.GetTrades(ctx, p.Quote.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]trade.Data, len(tradeData))
|
||||
for i := range tradeData {
|
||||
resp[i] = trade.Data{
|
||||
Exchange: l.Name,
|
||||
TID: strconv.FormatInt(tradeData[i].TID, 10),
|
||||
CurrencyPair: p,
|
||||
AssetType: assetType,
|
||||
Price: tradeData[i].Price,
|
||||
Amount: tradeData[i].Amount,
|
||||
Timestamp: time.Unix(tradeData[i].Date, 0),
|
||||
}
|
||||
}
|
||||
|
||||
err = l.AddTradesToBuffer(resp...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Sort(trade.ByDate(resp))
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetHistoricTrades returns historic trade data within the timeframe provided
|
||||
func (l *LocalBitcoins) GetHistoricTrades(_ context.Context, _ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
func (l *LocalBitcoins) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) {
|
||||
if err := s.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fPair, err := l.FormatExchangeCurrency(s.Pair, s.AssetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// These are placeholder details
|
||||
// TODO store a user's localbitcoin details to use here
|
||||
var params = AdCreate{
|
||||
PriceEquation: "USD_in_AUD",
|
||||
Latitude: 1,
|
||||
Longitude: 1,
|
||||
City: "City",
|
||||
Location: "Location",
|
||||
CountryCode: "US",
|
||||
Currency: fPair.Quote.String(),
|
||||
AccountInfo: "-",
|
||||
BankName: "Bank",
|
||||
MSG: s.Side.String(),
|
||||
SMSVerficationRequired: true,
|
||||
TrackMaxAmount: true,
|
||||
RequireTrustedByAdvertiser: true,
|
||||
RequireIdentification: true,
|
||||
OnlineProvider: "",
|
||||
TradeType: "",
|
||||
MinAmount: int(math.Round(s.Amount)),
|
||||
}
|
||||
|
||||
// Does not return any orderID, so create the add, then get the order
|
||||
err = l.CreateAd(ctx, ¶ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now to figure out what ad we just submitted
|
||||
// The only details we have are the params above
|
||||
ads, err := l.Getads(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var adID string
|
||||
for i := range ads.AdList {
|
||||
if ads.AdList[i].Data.PriceEquation == params.PriceEquation &&
|
||||
ads.AdList[i].Data.Lat == float64(params.Latitude) &&
|
||||
ads.AdList[i].Data.Lon == float64(params.Longitude) &&
|
||||
ads.AdList[i].Data.City == params.City &&
|
||||
ads.AdList[i].Data.Location == params.Location &&
|
||||
ads.AdList[i].Data.CountryCode == params.CountryCode &&
|
||||
ads.AdList[i].Data.Currency == params.Currency &&
|
||||
ads.AdList[i].Data.AccountInfo == params.AccountInfo &&
|
||||
ads.AdList[i].Data.BankName == params.BankName &&
|
||||
ads.AdList[i].Data.SMSVerficationRequired == params.SMSVerficationRequired &&
|
||||
ads.AdList[i].Data.TrackMaxAmount == params.TrackMaxAmount &&
|
||||
ads.AdList[i].Data.RequireTrustedByAdvertiser == params.RequireTrustedByAdvertiser &&
|
||||
ads.AdList[i].Data.OnlineProvider == params.OnlineProvider &&
|
||||
ads.AdList[i].Data.TradeType == params.TradeType &&
|
||||
ads.AdList[i].Data.MinAmount == strconv.FormatInt(int64(params.MinAmount), 10) {
|
||||
adID = strconv.FormatInt(ads.AdList[i].Data.AdID, 10)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if adID == "" {
|
||||
return nil, errors.New("ad placed, but not found via API")
|
||||
}
|
||||
return s.DeriveSubmitResponse(adID)
|
||||
}
|
||||
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (l *LocalBitcoins) ModifyOrder(_ context.Context, _ *order.Modify) (*order.ModifyResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (l *LocalBitcoins) CancelOrder(ctx context.Context, o *order.Cancel) error {
|
||||
if err := o.Validate(o.StandardCancel()); err != nil {
|
||||
return err
|
||||
}
|
||||
return l.DeleteAd(ctx, o.OrderID)
|
||||
}
|
||||
|
||||
// CancelBatchOrders cancels an orders by their corresponding ID numbers
|
||||
func (l *LocalBitcoins) CancelBatchOrders(ctx context.Context, o []order.Cancel) (order.CancelBatchResponse, error) {
|
||||
return order.CancelBatchResponse{}, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
func (l *LocalBitcoins) CancelAllOrders(ctx context.Context, _ *order.Cancel) (order.CancelAllResponse, error) {
|
||||
cancelAllOrdersResponse := order.CancelAllResponse{
|
||||
Status: make(map[string]string),
|
||||
}
|
||||
ads, err := l.Getads(ctx)
|
||||
if err != nil {
|
||||
return cancelAllOrdersResponse, err
|
||||
}
|
||||
|
||||
for i := range ads.AdList {
|
||||
adIDString := strconv.FormatInt(ads.AdList[i].Data.AdID, 10)
|
||||
err = l.DeleteAd(ctx, adIDString)
|
||||
if err != nil {
|
||||
cancelAllOrdersResponse.Status[adIDString] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
return cancelAllOrdersResponse, nil
|
||||
}
|
||||
|
||||
// GetOrderInfo returns order information based on order ID
|
||||
func (l *LocalBitcoins) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (order.Detail, error) {
|
||||
var orderDetail order.Detail
|
||||
return orderDetail, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (l *LocalBitcoins) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, _ string) (*deposit.Address, error) {
|
||||
if !strings.EqualFold(currency.BTC.String(), cryptocurrency.String()) {
|
||||
return nil, fmt.Errorf("%s does not have support for currency %s, it only supports bitcoin",
|
||||
l.Name, cryptocurrency)
|
||||
}
|
||||
|
||||
depositAddr, err := l.GetWalletAddress(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &deposit.Address{Address: depositAddr}, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
// submitted
|
||||
func (l *LocalBitcoins) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
if err := withdrawRequest.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err := l.WalletSend(ctx,
|
||||
withdrawRequest.Crypto.Address,
|
||||
withdrawRequest.Amount,
|
||||
withdrawRequest.PIN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &withdraw.ExchangeResponse{}, nil
|
||||
}
|
||||
|
||||
// WithdrawFiatFunds returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (l *LocalBitcoins) WithdrawFiatFunds(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (l *LocalBitcoins) WithdrawFiatFundsToInternationalBank(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (l *LocalBitcoins) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
if feeBuilder == nil {
|
||||
return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer)
|
||||
}
|
||||
if (!l.AreCredentialsValid(ctx) || l.SkipAuthCheck) && // Todo check connection status
|
||||
feeBuilder.FeeType == exchange.CryptocurrencyTradeFee {
|
||||
feeBuilder.FeeType = exchange.OfflineTradeFee
|
||||
}
|
||||
return l.GetFee(feeBuilder)
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (l *LocalBitcoins) GetActiveOrders(ctx context.Context, getOrdersRequest *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
err := getOrdersRequest.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := l.GetDashboardInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := l.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orders := make([]order.Detail, len(resp))
|
||||
for i := range resp {
|
||||
var orderDate time.Time
|
||||
orderDate, err = time.Parse(time.RFC3339, resp[i].Data.CreatedAt)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v",
|
||||
l.Name,
|
||||
"GetActiveOrders",
|
||||
resp[i].Data.Advertisement.ID,
|
||||
resp[i].Data.CreatedAt)
|
||||
}
|
||||
|
||||
side := order.Buy
|
||||
if resp[i].Data.IsSelling {
|
||||
side = order.Sell
|
||||
}
|
||||
|
||||
orders[i] = order.Detail{
|
||||
Amount: resp[i].Data.AmountBTC,
|
||||
Price: resp[i].Data.Amount,
|
||||
OrderID: strconv.FormatInt(resp[i].Data.Advertisement.ID, 10),
|
||||
Date: orderDate,
|
||||
Fee: resp[i].Data.FeeBTC,
|
||||
Side: side,
|
||||
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
resp[i].Data.Currency,
|
||||
format.Delimiter),
|
||||
Exchange: l.Name,
|
||||
}
|
||||
}
|
||||
return getOrdersRequest.Filter(l.Name, orders), nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (l *LocalBitcoins) GetOrderHistory(ctx context.Context, getOrdersRequest *order.GetOrdersRequest) (order.FilteredOrders, error) {
|
||||
err := getOrdersRequest.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var allTrades []DashBoardInfo
|
||||
resp, err := l.GetDashboardCancelledTrades(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allTrades = append(allTrades, resp...)
|
||||
|
||||
resp, err = l.GetDashboardClosedTrades(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allTrades = append(allTrades, resp...)
|
||||
|
||||
resp, err = l.GetDashboardReleasedTrades(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allTrades = append(allTrades, resp...)
|
||||
|
||||
format, err := l.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orders := make([]order.Detail, len(allTrades))
|
||||
for i := range allTrades {
|
||||
var orderDate time.Time
|
||||
orderDate, err = time.Parse(time.RFC3339, allTrades[i].Data.CreatedAt)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"Exchange %v Func %v Order %v Could not parse date to unix with value of %v",
|
||||
l.Name,
|
||||
"GetActiveOrders",
|
||||
allTrades[i].Data.Advertisement.ID,
|
||||
allTrades[i].Data.CreatedAt)
|
||||
}
|
||||
|
||||
var side order.Side
|
||||
if allTrades[i].Data.IsBuying {
|
||||
side = order.Buy
|
||||
} else if allTrades[i].Data.IsSelling {
|
||||
side = order.Sell
|
||||
}
|
||||
|
||||
status := ""
|
||||
|
||||
switch {
|
||||
case allTrades[i].Data.ReleasedAt != "" &&
|
||||
allTrades[i].Data.ReleasedAt != null:
|
||||
status = "Released"
|
||||
case allTrades[i].Data.CanceledAt != "" &&
|
||||
allTrades[i].Data.CanceledAt != null:
|
||||
status = "Cancelled"
|
||||
case allTrades[i].Data.ClosedAt != "" &&
|
||||
allTrades[i].Data.ClosedAt != null:
|
||||
status = "Closed"
|
||||
}
|
||||
|
||||
var orderStatus order.Status
|
||||
orderStatus, err = order.StringToOrderStatus(status)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s %v", l.Name, err)
|
||||
}
|
||||
|
||||
orders[i] = order.Detail{
|
||||
Amount: allTrades[i].Data.AmountBTC,
|
||||
Price: allTrades[i].Data.Amount,
|
||||
OrderID: strconv.FormatInt(allTrades[i].Data.Advertisement.ID, 10),
|
||||
Date: orderDate,
|
||||
Fee: allTrades[i].Data.FeeBTC,
|
||||
Side: side,
|
||||
Status: orderStatus,
|
||||
Pair: currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
allTrades[i].Data.Currency,
|
||||
format.Delimiter),
|
||||
Exchange: l.Name,
|
||||
}
|
||||
}
|
||||
return getOrdersRequest.Filter(l.Name, orders), nil
|
||||
}
|
||||
|
||||
// ValidateCredentials validates current credentials used for wrapper
|
||||
// functionality
|
||||
func (l *LocalBitcoins) ValidateCredentials(ctx context.Context, assetType asset.Item) error {
|
||||
_, err := l.UpdateAccountInfo(ctx, assetType)
|
||||
return l.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (l *LocalBitcoins) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (l *LocalBitcoins) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package localbitcoins
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const orderBookLimiter request.EndpointLimit = 1
|
||||
const tickerLimiter request.EndpointLimit = 2
|
||||
|
||||
// RateLimit define s custom rate limiter scoped for orderbook requests
|
||||
type RateLimit struct {
|
||||
Orderbook *rate.Limiter
|
||||
Ticker *rate.Limiter
|
||||
}
|
||||
|
||||
// Limit executes rate limiting functionality for Binance
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
if f == orderBookLimiter {
|
||||
time.Sleep(r.Orderbook.Reserve().Delay())
|
||||
} else if f == tickerLimiter {
|
||||
time.Sleep(r.Ticker.Reserve().Delay())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
func SetRateLimit() *RateLimit {
|
||||
return &RateLimit{
|
||||
// 4 seconds per book fetching is the best time frame to actually
|
||||
// receive without retrying. There is undocumentated rate limit.
|
||||
Orderbook: request.NewRateLimit(4*time.Second, 1),
|
||||
Ticker: request.NewRateLimit(time.Second, 1),
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ var Exchanges = []string{
|
||||
"itbit",
|
||||
"kraken",
|
||||
"lbank",
|
||||
"localbitcoins",
|
||||
"okcoin international",
|
||||
"okx",
|
||||
"poloniex",
|
||||
|
||||
@@ -81,7 +81,6 @@ _b in this context is an `IBotExchange` implemented struct_
|
||||
| ItBit | Yes | NA | No |
|
||||
| Kraken | Yes | Yes | No |
|
||||
| Lbank | Yes | No | Yes |
|
||||
| LocalBitcoins | Yes | NA | No |
|
||||
| OKCoin International | Yes | Yes | No |
|
||||
| Okx | Yes | Yes | Yes |
|
||||
| Poloniex | Yes | Yes | Yes |
|
||||
|
||||
76
testdata/configtest.json
vendored
76
testdata/configtest.json
vendored
@@ -1949,82 +1949,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LocalBitcoins",
|
||||
"enabled": true,
|
||||
"verbose": false,
|
||||
"httpTimeout": 15000000000,
|
||||
"websocketResponseCheckTimeout": 30000000,
|
||||
"websocketResponseMaxLimit": 7000000000,
|
||||
"websocketTrafficTimeout": 30000000000,
|
||||
"websocketOrderbookBufferLimit": 5,
|
||||
"baseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR",
|
||||
"currencyPairs": {
|
||||
"requestFormat": {
|
||||
"uppercase": true
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true
|
||||
},
|
||||
"useGlobalFormat": true,
|
||||
"assetTypes": [
|
||||
"spot"
|
||||
],
|
||||
"pairs": {
|
||||
"spot": {
|
||||
"enabled": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR",
|
||||
"available": "BTCRUB,BTCRON,BTCXRP,BTCIRR,BTCKRW,BTCCNY,BTCRWF,BTCRSD,BTCOMR,BTCGTQ,BTCKES,BTCCOP,BTCJPY,BTCIDR,BTCBAM,BTCBDT,BTCDOP,BTCMWK,BTCUGX,BTCAOA,BTCAWG,BTCNZD,BTCGBP,BTCBOB,BTCCHF,BTCBYN,BTCLTC,BTCBRL,BTCTWD,BTCCRC,BTCPKR,BTCMXN,BTCVND,BTCDKK,BTCETB,BTCSEK,BTCAED,BTCTHB,BTCEUR,BTCARS,BTCUAH,BTCCAD,BTCPYG,BTCPEN,BTCUSD,BTCETH,BTCLKR,BTCTTD,BTCMYR,BTCHRK,BTCILS,BTCJOD,BTCKWD,BTCHKD,BTCTRY,BTCPLN,BTCZAR,BTCXOF,BTCSAR,BTCUYU,BTCTZS,BTCVES,BTCXAF,BTCGHS,BTCSGD,BTCNOK,BTCINR,BTCEGP,BTCAUD,BTCZMW,BTCGEL,BTCPAB,BTCCLP,BTCCZK,BTCMAD,BTCNGN,BTCQAR,BTCMDL,BTCCDF,BTCKZT,BTCPHP"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"authenticatedSupport": false,
|
||||
"authenticatedWebsocketApiSupport": false,
|
||||
"endpoints": {
|
||||
"url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
|
||||
},
|
||||
"credentials": {
|
||||
"key": "Key",
|
||||
"secret": "Secret"
|
||||
},
|
||||
"credentialsValidator": {
|
||||
"requiresKey": true,
|
||||
"requiresSecret": true
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"supports": {
|
||||
"restAPI": true,
|
||||
"restCapabilities": {
|
||||
"tickerBatching": true,
|
||||
"autoPairUpdates": true
|
||||
},
|
||||
"websocketAPI": false,
|
||||
"websocketCapabilities": {}
|
||||
},
|
||||
"enabled": {
|
||||
"autoPairUpdates": true,
|
||||
"websocketAPI": false
|
||||
}
|
||||
},
|
||||
"bankAccounts": [
|
||||
{
|
||||
"enabled": false,
|
||||
"bankName": "",
|
||||
"bankAddress": "",
|
||||
"bankPostalCode": "",
|
||||
"bankPostalCity": "",
|
||||
"bankCountry": "",
|
||||
"accountName": "",
|
||||
"accountNumber": "",
|
||||
"swiftCode": "",
|
||||
"iban": "",
|
||||
"supportedCurrencies": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "OKCOIN International",
|
||||
"enabled": true,
|
||||
|
||||
1
testdata/exchangelist.csv
vendored
1
testdata/exchangelist.csv
vendored
@@ -18,7 +18,6 @@ huobi,
|
||||
itbit,
|
||||
kraken,
|
||||
lbank,
|
||||
localbitcoins,
|
||||
okcoin international,
|
||||
okx,
|
||||
poloniex,
|
||||
|
||||
|
7686
testdata/http_mock/localbitcoins/localbitcoins.json
vendored
7686
testdata/http_mock/localbitcoins/localbitcoins.json
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user