storj/satellite/payments/storjscan/client.go
dlamarmorgan 270204f352 satellite/{payments/storjscan,satellitedb}: Add wallet implementation
Add storjscan wallets implementation to the satellite. The wallets interface allows you to add and claim new wallets as called by the API. The storjscan specific implementation of this interface uses a wallets DB to associate the user to a wallet address, as well as a storjscan client to request and associate new wallets to the satellite.

Change-Id: I54081edb5545d4e3ee07cf1cce3d3e87cc00c4a1
2022-06-03 11:45:47 +00:00

168 lines
4.1 KiB
Go

// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
package storjscan
import (
"context"
"encoding/json"
"math/big"
"net/http"
"strconv"
"time"
"github.com/zeebo/errs"
"storj.io/storj/private/blockchain"
)
var (
// ClientErr is general purpose storjscan client error class.
ClientErr = errs.Class("storjscan client")
// ClientErrUnauthorized is unauthorized err storjscan client error class.
ClientErrUnauthorized = errs.Class("storjscan client unauthorized")
)
// Header holds ethereum blockchain block header data.
type Header struct {
Hash blockchain.Hash
Number int64
Timestamp time.Time
}
// Payment holds storjscan payment data.
type Payment struct {
From blockchain.Address
To blockchain.Address
TokenValue *big.Int
BlockHash blockchain.Hash
BlockNumber int64
Transaction blockchain.Hash
LogIndex int
Timestamp time.Time
}
// LatestPayments contains latest payments and latest chain block header.
type LatestPayments struct {
LatestBlock Header
Payments []Payment
}
// Client is storjscan HTTP API client.
type Client struct {
endpoint string
identifier string
secret string
http http.Client
}
// NewClient creates new storjscan API client.
func NewClient(endpoint, identifier, secret string) *Client {
return &Client{
endpoint: endpoint,
identifier: identifier,
secret: secret,
http: http.Client{},
}
}
// Payments retrieves all payments after specified block for wallets associated with particular API key.
func (client *Client) Payments(ctx context.Context, from int64) (_ LatestPayments, err error) {
defer mon.Task()(&ctx)(&err)
p := client.endpoint + "/api/v0/tokens/payments"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p, nil)
if err != nil {
return LatestPayments{}, ClientErr.Wrap(err)
}
req.SetBasicAuth(client.identifier, client.secret)
query := req.URL.Query()
query.Set("from", strconv.FormatInt(from, 10))
req.URL.RawQuery = query.Encode()
resp, err := client.http.Do(req)
if err != nil {
return LatestPayments{}, ClientErr.Wrap(err)
}
defer func() {
err = errs.Combine(err, ClientErr.Wrap(resp.Body.Close()))
}()
if resp.StatusCode != http.StatusOK {
var data struct {
Error string `json:"error"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return LatestPayments{}, ClientErr.Wrap(err)
}
switch resp.StatusCode {
case http.StatusUnauthorized:
return LatestPayments{}, ClientErrUnauthorized.New("%s", data.Error)
default:
return LatestPayments{}, ClientErr.New("%s", data.Error)
}
}
var payments LatestPayments
if err := json.NewDecoder(resp.Body).Decode(&payments); err != nil {
return LatestPayments{}, ClientErr.Wrap(err)
}
return payments, nil
}
// ClaimNewEthAddress claims a new ethereum wallet address for the given user.
func (client *Client) ClaimNewEthAddress(ctx context.Context) (_ blockchain.Address, err error) {
defer mon.Task()(&ctx)(&err)
p := client.endpoint + "/api/v0/wallets/claim"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p, nil)
if err != nil {
return blockchain.Address{}, ClientErr.Wrap(err)
}
req.SetBasicAuth(client.identifier, client.secret)
resp, err := client.http.Do(req)
if err != nil {
return blockchain.Address{}, ClientErr.Wrap(err)
}
defer func() {
err = errs.Combine(err, ClientErr.Wrap(resp.Body.Close()))
}()
if resp.StatusCode != http.StatusOK {
var data struct {
Error string `json:"error"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return blockchain.Address{}, ClientErr.Wrap(err)
}
switch resp.StatusCode {
case http.StatusUnauthorized:
return blockchain.Address{}, ClientErrUnauthorized.New("%s", data.Error)
default:
return blockchain.Address{}, ClientErr.New("%s", data.Error)
}
}
var addressHex string
if err = json.NewDecoder(resp.Body).Decode(&addressHex); err != nil {
return blockchain.Address{}, ClientErr.Wrap(err)
}
var address blockchain.Address
if err = address.UnmarshalJSON([]byte(addressHex)); err != nil {
return blockchain.Address{}, ClientErr.Wrap(err)
}
return address, nil
}