mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
* feat: add both Sat and Msat companion fields for all ambiguous balance/amount properties * feat: add sat and msat companion fields to backend API responses * chore: align frontend types and other missing fields * chore: keep old formula for calculating total fee sat * chore: remove unnecessary balance assignments in cashu and phoenix * chore: add deprecated comment to non unit fields * chore: rename callers and variable to specify units * chore: remove msat fields for channel size and liquidity fields * chore: drop msat fields from onchain channel size and liquidity responses * chore: further changes * chore: remove amount msat field onchain tx * chore: remove msats from onchain balance response * chore: remove msat fields for swaps * chore: remove msat fields for punishment reserves * chore: simplify rebalancing fee calculation * chore: remove unnecessary fields * chore: mark deprecated fields in frontend types --------- Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
262 lines
7.3 KiB
Go
262 lines
7.3 KiB
Go
package alby
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/getAlby/hub/logger"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const albyInternalAPIURL = "https://getalby.com/api"
|
|
|
|
type albyService struct {
|
|
}
|
|
|
|
func NewAlbyService() *albyService {
|
|
return &albyService{}
|
|
}
|
|
|
|
func (svc *albyService) GetCurrencies(ctx context.Context) ([]Currency, error) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
url := fmt.Sprintf("%s/rates", albyInternalAPIURL)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Error creating request to currencies endpoint")
|
|
return nil, err
|
|
}
|
|
setDefaultRequestHeaders(req)
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to fetch currencies from API")
|
|
return nil, err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
|
"url": url,
|
|
}).Error("Failed to read response body")
|
|
return nil, errors.New("failed to read response body")
|
|
}
|
|
|
|
if res.StatusCode >= 300 {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"body": string(body),
|
|
"status_code": res.StatusCode,
|
|
}).Error("Currencies endpoint returned non-success code")
|
|
return nil, fmt.Errorf("currencies endpoint returned non-success code: %s", string(body))
|
|
}
|
|
|
|
rawCurrencies := map[string]Currency{}
|
|
err = json.Unmarshal(body, &rawCurrencies)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"body": string(body),
|
|
"error": err,
|
|
}).Error("Failed to decode currencies API response")
|
|
return nil, err
|
|
}
|
|
|
|
currencies := []Currency{}
|
|
for _, currency := range rawCurrencies {
|
|
currencies = append(currencies, currency)
|
|
}
|
|
|
|
return currencies, nil
|
|
}
|
|
|
|
func (svc *albyService) GetBitcoinRate(ctx context.Context, currency string) (*BitcoinRate, error) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
|
|
url := fmt.Sprintf("%s/rates/%s", albyInternalAPIURL, currency)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"currency": currency,
|
|
"error": err,
|
|
}).Error("Error creating request to Bitcoin rate endpoint")
|
|
return nil, err
|
|
}
|
|
setDefaultRequestHeaders(req)
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"currency": currency,
|
|
"error": err,
|
|
}).Error("Failed to fetch Bitcoin rate from API")
|
|
return nil, err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
|
"url": url,
|
|
}).Error("Failed to read response body")
|
|
return nil, errors.New("failed to read response body")
|
|
}
|
|
|
|
if res.StatusCode >= 300 {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"currency": currency,
|
|
"body": string(body),
|
|
"status_code": res.StatusCode,
|
|
}).Error("Bitcoin rate endpoint returned non-success code")
|
|
return nil, fmt.Errorf("bitcoin rate endpoint returned non-success code: %s", string(body))
|
|
}
|
|
|
|
var rate = &BitcoinRate{}
|
|
err = json.Unmarshal(body, rate)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"currency": currency,
|
|
"body": string(body),
|
|
"error": err,
|
|
}).Error("Failed to decode Bitcoin rate API response")
|
|
return nil, err
|
|
}
|
|
|
|
return rate, nil
|
|
}
|
|
|
|
func (svc *albyService) GetChannelPeerSuggestions(ctx context.Context) ([]ChannelPeerSuggestion, error) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/internal/channel_suggestions", albyInternalAPIURL), nil)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Error creating request to channel_suggestions endpoint")
|
|
return nil, err
|
|
}
|
|
|
|
setDefaultRequestHeaders(req)
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to fetch channel_suggestions endpoint")
|
|
return nil, err
|
|
}
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to read response body")
|
|
return nil, errors.New("failed to read response body")
|
|
}
|
|
|
|
if res.StatusCode >= 300 {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"body": string(body),
|
|
"status_code": res.StatusCode,
|
|
}).Error("channel suggestions endpoint returned non-success code")
|
|
return nil, fmt.Errorf("channel suggestions endpoint returned non-success code: %s", string(body))
|
|
}
|
|
|
|
var suggestions []ChannelPeerSuggestion
|
|
err = json.Unmarshal(body, &suggestions)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Errorf("Failed to decode API response")
|
|
return nil, err
|
|
}
|
|
|
|
for i := range suggestions {
|
|
suggestions[i].MinimumChannelSizeSat = suggestions[i].MinimumChannelSize
|
|
suggestions[i].MaximumChannelSizeSat = suggestions[i].MaximumChannelSize
|
|
}
|
|
|
|
logger.Logger.WithFields(logrus.Fields{"channel_suggestions": suggestions}).Debug("Alby channel peer suggestions response")
|
|
return suggestions, nil
|
|
}
|
|
|
|
func (svc *albyService) GetInfo(ctx context.Context) (*AlbyInfo, error) {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/internal/info", albyInternalAPIURL), nil)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Error creating request to alby info endpoint")
|
|
return nil, err
|
|
}
|
|
|
|
setDefaultRequestHeaders(req)
|
|
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to fetch /info")
|
|
return nil, err
|
|
}
|
|
|
|
type albyInfoHub struct {
|
|
LatestVersion string `json:"latest_version"`
|
|
LatestReleaseNotes string `json:"latest_release_notes"`
|
|
}
|
|
|
|
type albyInfoIncident struct {
|
|
Name string `json:"name"`
|
|
Started string `json:"started"`
|
|
Status string `json:"status"`
|
|
Impact string `json:"impact"`
|
|
Url string `json:"url"`
|
|
}
|
|
|
|
type albyInfo struct {
|
|
Hub albyInfoHub `json:"hub"`
|
|
Status string `json:"status"`
|
|
Healthy bool `json:"healthy"`
|
|
AccountAvailable bool `json:"account_available"` // false if country is blocked (can still use Alby Hub without an Alby Account)
|
|
Incidents []albyInfoIncident `json:"incidents"`
|
|
}
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to read response body")
|
|
return nil, errors.New("failed to read response body")
|
|
}
|
|
|
|
if res.StatusCode >= 300 {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"body": string(body),
|
|
"status_code": res.StatusCode,
|
|
}).Error("info endpoint returned non-success code")
|
|
return nil, fmt.Errorf("info endpoint returned non-success code: %s", string(body))
|
|
}
|
|
|
|
info := &albyInfo{}
|
|
err = json.Unmarshal(body, info)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to decode API response")
|
|
return nil, err
|
|
}
|
|
|
|
incidents := []AlbyInfoIncident{}
|
|
for _, incident := range info.Incidents {
|
|
incidents = append(incidents, AlbyInfoIncident{
|
|
Name: incident.Name,
|
|
Started: incident.Started,
|
|
Status: incident.Status,
|
|
Impact: incident.Impact,
|
|
Url: incident.Url,
|
|
})
|
|
}
|
|
|
|
return &AlbyInfo{
|
|
Hub: AlbyInfoHub{
|
|
LatestVersion: info.Hub.LatestVersion,
|
|
LatestReleaseNotes: info.Hub.LatestReleaseNotes,
|
|
},
|
|
Status: info.Status,
|
|
Healthy: info.Healthy,
|
|
AccountAvailable: info.AccountAvailable,
|
|
Incidents: incidents,
|
|
}, nil
|
|
}
|