2019-10-11 16:51:14 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package coinpayments
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Error is error class API errors.
|
|
|
|
var Error = errs.Class("coinpayments client error")
|
|
|
|
|
2020-05-22 11:42:17 +01:00
|
|
|
// ErrMissingPublicKey is returned when Coinpayments client is missing public key.
|
|
|
|
var ErrMissingPublicKey = errs.Class("missing public key")
|
|
|
|
|
2019-10-11 16:51:14 +01:00
|
|
|
// Credentials contains public and private API keys for client.
|
|
|
|
type Credentials struct {
|
|
|
|
PublicKey string
|
|
|
|
PrivateKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client handles base API processing.
|
|
|
|
type Client struct {
|
|
|
|
creds Credentials
|
|
|
|
http http.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewClient creates new instance of client with provided credentials.
|
|
|
|
func NewClient(creds Credentials) *Client {
|
|
|
|
client := &Client{
|
|
|
|
creds: creds,
|
|
|
|
http: http.Client{
|
|
|
|
Timeout: 0,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return client
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transactions returns transactions API.
|
|
|
|
func (c *Client) Transactions() Transactions {
|
|
|
|
return Transactions{client: c}
|
|
|
|
}
|
|
|
|
|
2019-11-15 14:59:39 +00:00
|
|
|
// ConversionRates returns ConversionRates API.
|
|
|
|
func (c *Client) ConversionRates() ConversionRates {
|
|
|
|
return ConversionRates{client: c}
|
|
|
|
}
|
|
|
|
|
2019-10-11 16:51:14 +01:00
|
|
|
// do handles base API request routines.
|
|
|
|
func (c *Client) do(ctx context.Context, cmd string, values url.Values) (_ json.RawMessage, err error) {
|
2020-05-22 11:42:17 +01:00
|
|
|
if c.creds.PublicKey == "" {
|
|
|
|
return nil, Error.Wrap(ErrMissingPublicKey.New(""))
|
|
|
|
}
|
|
|
|
|
2019-10-11 16:51:14 +01:00
|
|
|
values.Set("version", "1")
|
|
|
|
values.Set("format", "json")
|
|
|
|
values.Set("key", c.creds.PublicKey)
|
|
|
|
values.Set("cmd", cmd)
|
|
|
|
|
|
|
|
encoded := values.Encode()
|
|
|
|
|
|
|
|
buff := bytes.NewBufferString(encoded)
|
|
|
|
|
|
|
|
req, err := http.NewRequest(http.MethodPost, "https://www.coinpayments.net/api.php", buff)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
req.Header.Set("HMAC", c.hmac([]byte(encoded)))
|
|
|
|
|
|
|
|
resp, err := c.http.Do(req.WithContext(ctx))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err = errs.Combine(err, resp.Body.Close())
|
|
|
|
}()
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return nil, errs.New("internal server error")
|
|
|
|
}
|
|
|
|
|
|
|
|
var data struct {
|
|
|
|
Error string `json:"error"`
|
|
|
|
Result json.RawMessage `json:"result"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.Error != "ok" {
|
|
|
|
return nil, errs.New(data.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
return data.Result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// hmac returns string representation of HMAC signature
|
|
|
|
// signed with clients private key.
|
|
|
|
func (c *Client) hmac(payload []byte) string {
|
|
|
|
mac := hmac.New(sha512.New, []byte(c.creds.PrivateKey))
|
|
|
|
_, _ = mac.Write(payload)
|
|
|
|
return hex.EncodeToString(mac.Sum(nil))
|
|
|
|
}
|