exchanges: Remove LocalBitcoins and fix Bybit tests (#1142)

This commit is contained in:
Adrian Gallagher
2023-02-22 15:23:18 +11:00
committed by GitHub
parent e94f9bb0a2
commit e44ae3d75f
32 changed files with 14 additions and 10638 deletions

View File

@@ -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 |

View File

@@ -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, "")

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 |

View File

@@ -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}}

View File

@@ -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 |

View File

@@ -141,11 +141,6 @@
"secret": "Secret",
"otpSecret": "-"
},
"localbitcoins": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"okcoin international": {
"key": "Key",
"secret": "Secret",

View File

@@ -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))
}

View File

@@ -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,

View File

@@ -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",

View File

@@ -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 | |

View File

@@ -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 |

View File

@@ -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":

View File

@@ -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" {

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -1,132 +0,0 @@
# GoCryptoTrader package Localbitcoins
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/localbitcoins)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](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***

View File

@@ -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
}

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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 := &currency.PairFormat{Uppercase: true}
configFmt := &currency.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, &params)
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
}

View File

@@ -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),
}
}

View File

@@ -35,7 +35,6 @@ var Exchanges = []string{
"itbit",
"kraken",
"lbank",
"localbitcoins",
"okcoin international",
"okx",
"poloniex",

View File

@@ -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 |

View File

@@ -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,

View File

@@ -18,7 +18,6 @@ huobi,
itbit,
kraken,
lbank,
localbitcoins,
okcoin international,
okx,
poloniex,
1 binanceus
18 itbit
19 kraken
20 lbank
localbitcoins
21 okcoin international
22 okx
23 poloniex

File diff suppressed because it is too large Load Diff